Files
olcrtc/magefile.go
2026-05-27 13:09:16 +03:00

367 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build mage
// Magefile for olcrtc.
//
// Quick reference:
//
// mage check # build + vet + lint + unit tests (pre-commit)
// mage all # full pre-merge pipeline (check + e2e smoke matrix)
// mage nightly # everything including stress matrix (~6h)
//
// mage build # native binary
// mage cross # all platforms
// mage mobile # Android AAR
//
// mage test # short unit tests
// mage testfull # all unit tests, no real providers
// mage e2e # real-provider smoke matrix
// mage stress # real-provider stress matrix (long)
// mage soak # real-provider throughput soak (long)
// mage localsoak # local in-memory soak (very long, no network)
//
// Tunables (env):
//
// E2E_CARRIERS, E2E_TRANSPORTS, E2E_TIMEOUT
// E2E_STRESS, E2E_STRESS_DURATION
// STRESS_BULK_DURATION, STRESS_ECHO_DURATION, STRESS_CASE_TIMEOUT, STRESS_TIMEOUT
// SOAK_CARRIERS, SOAK_TRANSPORTS, SOAK_DURATION, SOAK_CHAOS
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
// Default target when invoked as `mage` with no args.
//
//nolint:gochecknoglobals // mage requires a package-level Default symbol.
var Default = Help
const (
buildDir = "build"
ldflags = "-s -w"
)
var (
goexe = mg.GoCmd()
goos = envOr("GOOS", runtime.GOOS)
goarch = envOr("GOARCH", runtime.GOARCH)
)
// ─────────────────────────────────────────────────────────────────────────────
// Pipelines
// ─────────────────────────────────────────────────────────────────────────────
// Help lists every target in mage's default style. This is what runs when
// you invoke `mage` with no arguments. For grouped/annotated docs see the
// magefile header.
func Help() error {
return sh.RunV("mage", "-l")
}
// Check runs the fast pre-commit pipeline: build + vet + lint + unit tests.
// Use this before every commit.
func Check() {
mg.SerialDeps(Build, Vet, Lint, TestFull)
}
// All runs the full pre-merge pipeline: Check + the real-provider smoke
// matrix. Stress and soak are NOT included - run them explicitly when
// needed (see Nightly for everything).
func All() {
mg.SerialDeps(Check, E2e)
}
// Nightly runs everything: All + stress matrix. Expect multi-hour runtime;
// intended for the nightly CI job or manual deep validation.
func Nightly() {
mg.SerialDeps(All, Stress)
}
// Everything runs literally every test stage: Nightly + real soak + local
// soak. Expect 12+ hours; this is for "I want maximum confidence before
// shipping" runs, not regular development. Tune SOAK_DURATION etc. if you
// need a shorter window.
func Everything() {
mg.SerialDeps(Nightly, Soak, LocalSoak)
}
// ─────────────────────────────────────────────────────────────────────────────
// Build
// ─────────────────────────────────────────────────────────────────────────────
// Build builds the olcrtc CLI binary for the host platform.
func Build() error {
mg.Deps(Deps)
return buildBinary("olcrtc", "./cmd/olcrtc", goos, goarch)
}
// Cross builds olcrtc for all supported platforms.
func Cross() error {
mg.Deps(Deps)
targets := []struct{ os, arch string }{
{"linux", "amd64"},
{"linux", "arm64"},
{"windows", "amd64"},
{"darwin", "amd64"},
{"darwin", "arm64"},
{"freebsd", "amd64"},
{"freebsd", "arm64"},
{"openbsd", "amd64"},
{"openbsd", "arm64"},
}
for _, t := range targets {
if err := buildBinary("olcrtc", "./cmd/olcrtc", t.os, t.arch); err != nil {
return err
}
}
fmt.Printf("✅ built %d platform(s)\n", len(targets))
return nil
}
// Mobile builds the Android AAR via gomobile.
func Mobile() error {
if err := ensureTool("gomobile"); err != nil {
return fmt.Errorf("gomobile not found: run 'go install golang.org/x/mobile/cmd/gomobile@latest && gomobile init'")
}
if err := ensureBuildDir(); err != nil {
return err
}
return sh.RunV("gomobile", "bind",
"-target=android",
"-androidapi", "21",
"-ldflags", "-s -w -checklinkname=0",
"-o", filepath.Join(buildDir, "olcrtc.aar"),
"./mobile",
)
}
// ─────────────────────────────────────────────────────────────────────────────
// Container
// ─────────────────────────────────────────────────────────────────────────────
// Docker builds the image using docker.
func Docker() error {
tag := envOr("DOCKER_TAG", "olcrtc:latest")
return sh.RunV("docker", "build", "-t", tag, ".")
}
// Podman builds the image using podman.
func Podman() error {
tag := envOr("DOCKER_TAG", "olcrtc:latest")
return sh.RunV("podman", "build", "-t", tag, ".")
}
// ─────────────────────────────────────────────────────────────────────────────
// Quality
// ─────────────────────────────────────────────────────────────────────────────
// Vet runs go vet on the whole module.
func Vet() error {
return sh.RunV(goexe, "vet", "./...")
}
// Lint runs golangci-lint.
func Lint() error {
if err := ensureTool("golangci-lint"); err != nil {
return fmt.Errorf("golangci-lint not found, install it:\n go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest")
}
return sh.RunV("golangci-lint", "run", "./...")
}
// Tidy runs go mod tidy and verifies modules.
func Tidy() error {
if err := sh.RunV(goexe, "mod", "tidy"); err != nil {
return err
}
return sh.RunV(goexe, "mod", "verify")
}
// ─────────────────────────────────────────────────────────────────────────────
// Tests
// ─────────────────────────────────────────────────────────────────────────────
// Test runs unit tests in -short mode (skips long-running tests).
func Test() error {
return sh.RunV(goexe, "test", "-race", "-count=1", "-short", "./...")
}
// TestFull runs all unit + fast e2e tests with race detector. No real providers.
func TestFull() error {
return sh.RunV(goexe, "test", "-race", "-count=1", "-timeout", "10m", "./...")
}
// E2e runs the real-provider smoke matrix.
// Configure via env: E2E_CARRIERS, E2E_TRANSPORTS, E2E_TIMEOUT, E2E_STRESS.
//
// Note: -race is intentionally NOT enabled here. The race detector adds
// significant CPU overhead and breaks timing-sensitive handshake tests
// against real networks (telemost/videochannel handshake regularly times
// out under -race). Race coverage for production code paths is provided
// by `mage testFull` against in-memory carriers.
func E2e() error {
args := []string{"test", "-count=1", "-v", "-timeout", "30m",
"./internal/e2e/...",
"-olcrtc.real-e2e=true",
}
if carriers := os.Getenv("E2E_CARRIERS"); carriers != "" {
args = append(args, "-olcrtc.real-carriers="+carriers)
}
if transports := os.Getenv("E2E_TRANSPORTS"); transports != "" {
args = append(args, "-olcrtc.real-transports="+transports)
}
if timeout := os.Getenv("E2E_TIMEOUT"); timeout != "" {
args = append(args, "-olcrtc.real-timeout="+timeout)
}
if os.Getenv("E2E_STRESS") != "" {
args = append(args, "-olcrtc.stress=true")
if d := os.Getenv("E2E_STRESS_DURATION"); d != "" {
args = append(args, "-olcrtc.stress-duration="+d)
}
}
return sh.RunV(goexe, args...)
}
// Stress runs the real-provider stress matrix on every carrier × transport pair.
// Defaults match the long nightly profile (15m bulk + 15m echo, 35m hard cap per case).
// Override via env: STRESS_BULK_DURATION, STRESS_ECHO_DURATION, STRESS_CASE_TIMEOUT,
// STRESS_TIMEOUT, E2E_CARRIERS, E2E_TRANSPORTS.
func Stress() error {
bulk := envOr("STRESS_BULK_DURATION", "15m")
echo := envOr("STRESS_ECHO_DURATION", "15m")
caseTO := envOr("STRESS_CASE_TIMEOUT", "35m")
overall := envOr("STRESS_TIMEOUT", "6h")
args := []string{"test", "-count=1", "-v",
"-timeout", overall,
"-run", "^TestRealProviderTransportStress$",
"./internal/e2e/...",
"-olcrtc.real-e2e=true",
"-olcrtc.stress=true",
"-olcrtc.stress-bulk-duration=" + bulk,
"-olcrtc.stress-duration=" + echo,
"-olcrtc.stress-case-timeout=" + caseTO,
}
if carriers := os.Getenv("E2E_CARRIERS"); carriers != "" {
args = append(args, "-olcrtc.real-carriers="+carriers)
}
if transports := os.Getenv("E2E_TRANSPORTS"); transports != "" {
args = append(args, "-olcrtc.real-transports="+transports)
}
return sh.RunV(goexe, args...)
}
// Soak runs the real-provider throughput soak test.
// Configure via env: SOAK_CARRIERS, SOAK_TRANSPORTS, SOAK_DURATION.
func Soak() error {
carriers := envOr("SOAK_CARRIERS", "telemost,jitsi,wbstream")
transports := envOr("SOAK_TRANSPORTS", "datachannel,vp8channel")
duration := envOr("SOAK_DURATION", "10m")
args := []string{"test", "-count=1", "-v",
"-timeout", "12h",
"-run", "^TestRealThroughputSoak$",
"./internal/e2e/...",
"-olcrtc.real-e2e=true",
"-olcrtc.real-soak=true",
"-olcrtc.real-soak-carrier=" + carriers,
"-olcrtc.real-soak-transport=" + transports,
"-olcrtc.real-soak-duration=" + duration,
}
return sh.RunV(goexe, args...)
}
// LocalSoak runs the local (in-memory) throughput soak.
// Configure via env: SOAK_TRANSPORTS, SOAK_DURATION, SOAK_CHAOS.
func LocalSoak() error {
transports := envOr("SOAK_TRANSPORTS", "all")
duration := envOr("SOAK_DURATION", "6m")
chaos := os.Getenv("SOAK_CHAOS")
args := []string{"test", "-count=1", "-v",
"-timeout", "12h",
"-run", "^TestLocalThroughputSoak$",
"./internal/e2e/...",
"-olcrtc.local-soak=true",
"-olcrtc.local-soak-transport=" + transports,
"-olcrtc.local-soak-duration=" + duration,
}
if chaos != "" {
args = append(args, "-olcrtc.local-soak-chaos="+chaos)
}
return sh.RunV(goexe, args...)
}
// ─────────────────────────────────────────────────────────────────────────────
// Utility
// ─────────────────────────────────────────────────────────────────────────────
// Deps downloads Go module dependencies.
func Deps() error {
return sh.RunV(goexe, "mod", "download")
}
// Clean removes build artifacts.
func Clean() error {
return os.RemoveAll(buildDir)
}
// ─────────────────────────────────────────────────────────────────────────────
// Helpers (unexported, not visible as targets)
// ─────────────────────────────────────────────────────────────────────────────
func buildBinary(name, pkg, os_, arch string) error {
if err := ensureBuildDir(); err != nil {
return err
}
ext := ""
if os_ == "windows" {
ext = ".exe"
}
outName := fmt.Sprintf("%s-%s-%s%s", name, os_, arch, ext)
out := filepath.Join(buildDir, outName)
fmt.Printf("building %s (%s/%s) -> %s\n", name, os_, arch, out)
env := map[string]string{
"GOOS": os_,
"GOARCH": arch,
"CGO_ENABLED": "0",
}
flags := ldflags
if os_ == "android" {
flags += " -checklinkname=0"
}
args := []string{"build", "-trimpath", "-ldflags", flags, "-o", out, pkg}
return sh.RunWithV(env, goexe, args...)
}
func ensureBuildDir() error {
return os.MkdirAll(buildDir, 0o755)
}
func ensureTool(name string) error {
_, err := exec.LookPath(name)
return err
}
func envOr(key, fallback string) string {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
return fallback
}