Files
bifrost/Makefile
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

1487 lines
68 KiB
Makefile

# Makefile for Bifrost
# Variables
HOST ?= localhost
PORT ?= 8080
APP_DIR ?=
PROMETHEUS_LABELS ?=
LOG_STYLE ?= json
LOG_LEVEL ?= info
TEST_REPORTS_DIR ?= test-reports
GOTESTSUM_FORMAT ?= standard-verbose
FLOW ?=
VERSION ?= dev-build
LOCAL ?=
DEBUG ?=
# Colors for output
RED=\033[0;31m
GREEN=\033[0;32m
YELLOW=\033[1;33m
BLUE=\033[0;34m
CYAN=\033[0;36m
NC=\033[0m # No Color
ECHO := printf '%b\n'
# nvm requires bash-compatible shell semantics; /bin/sh is dash on some Linux distros.
SHELL := /bin/bash
# Ensures the Node version pinned in .nvmrc is active before any npm/node call.
# nvm is a shell function, so each recipe that needs it must inline this snippet
# via `$(USE_NODE); <your command>`.
USE_NODE = NVM_SH="$${NVM_DIR:-$$HOME/.nvm}/nvm.sh"; \
[ -s "$$NVM_SH" ] || NVM_SH="$$(brew --prefix nvm 2>/dev/null)/nvm.sh"; \
if [ -s "$$NVM_SH" ]; then . "$$NVM_SH" >/dev/null && nvm install >/dev/null 2>&1 && nvm use >/dev/null 2>&1; fi
.PHONY: all help dev dev-pulse build-ui build build-cli run run-cli install-air install-pulse clean test test-cli install-ui setup-workspace work-init work-clean docs docker-image docker-run cleanup-enterprise mod-tidy test-integrations-py test-integrations-ts install-playwright run-e2e run-e2e-ui run-e2e-headed format ui
all: help
# Include deployment recipes
include recipes/fly.mk
include recipes/ecs.mk
include recipes/local-k8s.mk
# Default target
help: ## Show this help message
@$(ECHO) "$(BLUE)Bifrost Development - Available Commands:$(NC)"
@$(ECHO) ""
@grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(GREEN)%-15s$(NC) %s\n", $$1, $$2}'
@$(ECHO) ""
@$(ECHO) "$(YELLOW)Environment Variables:$(NC)"
@$(ECHO) " HOST Server host (default: localhost)"
@$(ECHO) " PORT Server port (default: 8080)"
@$(ECHO) " PROMETHEUS_LABELS Labels for Prometheus metrics"
@$(ECHO) " LOG_STYLE Logger output format: json|pretty (default: json)"
@$(ECHO) " LOG_LEVEL Logger level: debug|info|warn|error (default: info)"
@$(ECHO) " APP_DIR App data directory inside container (default: /app/data)"
@$(ECHO) " LOCAL Use local go.work for builds (e.g., make build LOCAL=1)"
@$(ECHO) " DEBUG Enable delve debugger on port 2345 (e.g., make dev DEBUG=1, make test-core DEBUG=1, make test-governance DEBUG=1)"
@$(ECHO) ""
@$(ECHO) "$(YELLOW)Test Configuration:$(NC)"
@$(ECHO) " TEST_REPORTS_DIR Directory for HTML test reports (default: test-reports)"
@$(ECHO) " GOTESTSUM_FORMAT Test output format: testname|dots|pkgname|standard-verbose (default: standard-verbose)"
@$(ECHO) " TESTCASE Exact test name to run (e.g., TestVirtualKeyTokenRateLimit)"
@$(ECHO) " PATTERN Substring pattern to filter tests (alternative to TESTCASE)"
@$(ECHO) " FLOW E2E test flow to run: providers|virtual-keys (default: all)"
cleanup-enterprise: ## Clean up enterprise directories if present
@$(ECHO) "$(GREEN)Cleaning up enterprise...$(NC)"
@if [ -d "ui/app/enterprise" ]; then rm -rf ui/app/enterprise; fi
@$(ECHO) "$(GREEN)Enterprise cleaned up$(NC)"
install-ui: cleanup-enterprise
@$(USE_NODE); \
which node > /dev/null || ($(ECHO) "$(RED)Error: Node.js is not installed. Please install Node.js first.$(NC)" && exit 1); \
which npm > /dev/null || ($(ECHO) "$(RED)Error: npm is not installed. Please install npm first.$(NC)" && exit 1); \
$(ECHO) "$(GREEN)Node.js $$(node -v) and npm $$(npm -v) are installed$(NC)"; \
if [ ! -d "ui/node_modules" ] || [ "ui/package.json" -nt "ui/node_modules/.package-lock.json" ] || [ "ui/package-lock.json" -nt "ui/node_modules/.package-lock.json" ]; then \
$(ECHO) "$(YELLOW)Dependencies changed, running npm ci...$(NC)"; \
cd ui && npm ci; \
else \
$(ECHO) "$(GREEN)UI dependencies up to date, skipping install$(NC)"; \
fi
@$(ECHO) "$(GREEN)UI deps are in sync$(NC)"
install-air: ## Install air for hot reloading (if not already installed)
@which air > /dev/null || ($(ECHO) "$(YELLOW)Installing air for hot reloading...$(NC)" && go install github.com/air-verse/air@latest)
@$(ECHO) "$(GREEN)Air is ready$(NC)"
install-pulse: ## Install pulse for hot reloading (if not already installed)
@which pulse > /dev/null || ($(ECHO) "$(YELLOW)Installing pulse for hot reloading...$(NC)" && go install github.com/Pratham-Mishra04/pulse@latest)
@$(ECHO) "$(GREEN)Pulse is ready$(NC)"
install-delve: ## Install delve for debugging (if not already installed)
@which dlv > /dev/null || ($(ECHO) "$(YELLOW)Installing delve for debugging...$(NC)" && go install github.com/go-delve/delve/cmd/dlv@latest)
@$(ECHO) "$(GREEN)Delve is ready$(NC)"
install-gotestsum: ## Install gotestsum for test reporting (if not already installed)
@which gotestsum > /dev/null || ($(ECHO) "$(YELLOW)Installing gotestsum for test reporting...$(NC)" && go install gotest.tools/gotestsum@latest)
@$(ECHO) "$(GREEN)gotestsum is ready$(NC)"
install-junit-viewer: ## Install junit-viewer for HTML report generation (if not already installed)
@if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(GREEN)junit-viewer is already installed$(NC)"; \
else \
$(ECHO) "$(YELLOW)Installing junit-viewer for HTML reports...$(NC)"; \
$(USE_NODE); \
if npm install -g junit-viewer 2>&1; then \
$(ECHO) "$(GREEN)junit-viewer installed successfully$(NC)"; \
else \
$(ECHO) "$(RED)Failed to install junit-viewer. HTML reports will be skipped.$(NC)"; \
$(ECHO) "$(YELLOW)You can install it manually: npm install -g junit-viewer$(NC)"; \
exit 0; \
fi; \
fi; \
else \
$(ECHO) "$(YELLOW)CI environment detected, skipping junit-viewer installation$(NC)"; \
fi
dev: install-ui install-air setup-workspace $(if $(DEBUG),install-delve) ## Start complete development environment (UI + API with proxy)
@$(ECHO) "$(GREEN)Starting Bifrost complete development environment...$(NC)"
@$(ECHO) "$(YELLOW)This will start:$(NC)"
@$(ECHO) " 1. UI development server (localhost:3000)"
@$(ECHO) " 2. API server with UI proxy (localhost:$(PORT))"
@$(ECHO) "$(CYAN)Access everything at: http://localhost:$(PORT)$(NC)"
@if [ -n "$(DEBUG)" ]; then \
$(ECHO) "$(CYAN) 3. Debugger (delve) listening on port 2345$(NC)"; \
fi
@if [ ! -d "transports/bifrost-http/ui" ]; then \
$(ECHO) "$(YELLOW)Creating transports/bifrost-http/ui directory...$(NC)"; \
mkdir -p transports/bifrost-http/ui; \
touch transports/bifrost-http/ui/.tmp; \
fi
@$(ECHO) ""
@$(ECHO) "$(YELLOW)Starting UI development server...$(NC)"
@$(USE_NODE); if [ -n "$(DISABLE_PROFILER)" ]; then \
$(ECHO) "$(CYAN)DevProfiler disabled for testing$(NC)"; \
cd ui && BIFROST_DISABLE_PROFILER=1 npm run dev & \
else \
cd ui && npm run dev & \
fi
@sleep 3
@$(ECHO) "$(YELLOW)Starting API server with UI proxy...$(NC)"
@$(MAKE) setup-workspace >/dev/null
@if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
if [ -n "$(DEBUG)" ]; then \
$(ECHO) "$(CYAN)Starting with air + delve debugger on port 2345...$(NC)"; \
$(ECHO) "$(YELLOW)Attach your debugger to localhost:2345$(NC)"; \
cd transports/bifrost-http && BIFROST_UI_DEV=true air -c .air.debug.toml -- \
-host "$(HOST)" \
-port "$(PORT)" \
-log-style "$(LOG_STYLE)" \
-log-level "$(LOG_LEVEL)" \
$(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") \
$(if $(APP_DIR),-app-dir "$(APP_DIR)"); \
else \
cd transports/bifrost-http && BIFROST_UI_DEV=true air -c .air.toml -- \
-host "$(HOST)" \
-port "$(PORT)" \
-log-style "$(LOG_STYLE)" \
-log-level "$(LOG_LEVEL)" \
$(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") \
$(if $(APP_DIR),-app-dir "$(APP_DIR)"); \
fi
dev-pulse: install-ui install-pulse setup-workspace $(if $(DEBUG),install-delve) ## Start complete development environment using pulse for hot reloading
@$(ECHO) "$(GREEN)Starting Bifrost complete development environment (pulse)...$(NC)"
@$(ECHO) "$(YELLOW)This will start:$(NC)"
@$(ECHO) " 1. UI development server (localhost:3000)"
@$(ECHO) " 2. API server with UI proxy (localhost:$(PORT))"
@$(ECHO) "$(CYAN)Access everything at: http://localhost:$(PORT)$(NC)"
@if [ -n "$(DEBUG)" ]; then \
$(ECHO) "$(CYAN) 3. Debugger (delve) listening on port 2345$(NC)"; \
fi
@if [ ! -d "transports/bifrost-http/ui" ]; then \
$(ECHO) "$(YELLOW)Creating transports/bifrost-http/ui directory...$(NC)"; \
mkdir -p transports/bifrost-http/ui; \
touch transports/bifrost-http/ui/.tmp; \
fi
@$(ECHO) ""
@$(ECHO) "$(YELLOW)Starting UI development server...$(NC)"
@$(USE_NODE); if [ -n "$(DISABLE_PROFILER)" ]; then \
$(ECHO) "$(CYAN)DevProfiler disabled for testing$(NC)"; \
cd ui && BIFROST_DISABLE_PROFILER=1 npm run dev & \
else \
cd ui && npm run dev & \
fi
@sleep 3
@$(ECHO) "$(YELLOW)Starting API server with UI proxy...$(NC)"
@$(MAKE) setup-workspace >/dev/null
@if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
if [ -n "$(DEBUG)" ]; then \
$(ECHO) "$(CYAN)Starting with pulse + delve debugger on port 2345...$(NC)"; \
$(ECHO) "$(YELLOW)Attach your debugger to localhost:2345$(NC)"; \
BIFROST_UI_DEV=true pulse -- \
-host "$(HOST)" \
-port "$(PORT)" \
-log-style "$(LOG_STYLE)" \
-log-level "$(LOG_LEVEL)" \
$(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") \
$(if $(APP_DIR),-app-dir "$(APP_DIR)"); \
else \
BIFROST_UI_DEV=true pulse -- \
-host "$(HOST)" \
-port "$(PORT)" \
-log-style "$(LOG_STYLE)" \
-log-level "$(LOG_LEVEL)" \
$(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") \
$(if $(APP_DIR),-app-dir "$(APP_DIR)"); \
fi
build-ui: install-ui ## Build ui
@$(ECHO) "$(GREEN)Building ui...$(NC)"
@rm -rf ui/.next
@$(USE_NODE); cd ui && npm run build && npm run copy-build
build: build-ui ## Build bifrost-http binary
@if [ -n "$(LOCAL)" ]; then \
$(ECHO) "$(GREEN)╔═══════════════════════════════════════════════╗$(NC)"; \
$(ECHO) "$(GREEN)║ Building bifrost-http with local go.work... ║$(NC)"; \
$(ECHO) "$(GREEN)╚═══════════════════════════════════════════════╝$(NC)"; \
else \
$(ECHO) "$(GREEN)╔═══════════════════════════════════════╗$(NC)"; \
$(ECHO) "$(GREEN)║ Building bifrost-http... ║$(NC)"; \
$(ECHO) "$(GREEN)╚═══════════════════════════════════════╝$(NC)"; \
fi
@if [ -n "$(DYNAMIC)" ]; then \
$(ECHO) "$(YELLOW)Note: This will create a dynamically linked build.$(NC)"; \
else \
$(ECHO) "$(YELLOW)Note: This will create a statically linked build.$(NC)"; \
fi
@mkdir -p ./tmp
@TARGET_OS="$(GOOS)"; \
TARGET_ARCH="$(GOARCH)"; \
ACTUAL_OS=$$(uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/darwin/;s/linux/linux/;s/mingw.*/windows/'); \
ACTUAL_ARCH=$$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/;s/arm64/arm64/'); \
if [ -z "$$TARGET_OS" ]; then \
TARGET_OS=$$ACTUAL_OS; \
fi; \
if [ -z "$$TARGET_ARCH" ]; then \
TARGET_ARCH=$$ACTUAL_ARCH; \
fi; \
HOST_OS=$$ACTUAL_OS; \
HOST_ARCH=$$ACTUAL_ARCH; \
$(ECHO) "$(CYAN)Host: $$HOST_OS/$$HOST_ARCH | Target: $$TARGET_OS/$$TARGET_ARCH$(NC)"; \
if [ "$$TARGET_OS" = "linux" ] && [ "$$HOST_OS" = "linux" ]; then \
if [ -n "$(DYNAMIC)" ]; then \
$(ECHO) "$(CYAN)Building for $$TARGET_OS/$$TARGET_ARCH with dynamic linking...$(NC)"; \
cd transports/bifrost-http && CGO_ENABLED=1 GOOS=$$TARGET_OS GOARCH=$$TARGET_ARCH $(if $(LOCAL),,GOWORK=off) go build \
-ldflags="-w -s -X main.Version=v$(VERSION)" \
-a -trimpath \
-o ../../tmp/bifrost-http \
.; \
else \
$(ECHO) "$(CYAN)Building for $$TARGET_OS/$$TARGET_ARCH with static linking...$(NC)"; \
cd transports/bifrost-http && CGO_ENABLED=1 GOOS=$$TARGET_OS GOARCH=$$TARGET_ARCH $(if $(LOCAL),,GOWORK=off) go build \
-ldflags="-w -s -extldflags "-static" -X main.Version=v$(VERSION)" \
-a -trimpath \
-tags "sqlite_static" \
-o ../../tmp/bifrost-http \
.; \
fi; \
$(ECHO) "$(GREEN)Built: tmp/bifrost-http (version: v$(VERSION))$(NC)"; \
elif [ "$$TARGET_OS" = "$$HOST_OS" ] && [ "$$TARGET_ARCH" = "$$HOST_ARCH" ]; then \
$(ECHO) "$(CYAN)Building for $$TARGET_OS/$$TARGET_ARCH (native build with CGO)...$(NC)"; \
cd transports/bifrost-http && CGO_ENABLED=1 GOOS=$$TARGET_OS GOARCH=$$TARGET_ARCH $(if $(LOCAL),,GOWORK=off) go build \
-ldflags="-w -s -X main.Version=v$(VERSION)" \
-a -trimpath \
-tags "sqlite_static" \
-o ../../tmp/bifrost-http \
.; \
$(ECHO) "$(GREEN)Built: tmp/bifrost-http (version: v$(VERSION))$(NC)"; \
else \
$(ECHO) "$(YELLOW)Cross-compilation detected: $$HOST_OS/$$HOST_ARCH -> $$TARGET_OS/$$TARGET_ARCH$(NC)"; \
$(ECHO) "$(CYAN)Using Docker for cross-compilation...$(NC)"; \
$(MAKE) _build-with-docker TARGET_OS=$$TARGET_OS TARGET_ARCH=$$TARGET_ARCH $(if $(DYNAMIC),DYNAMIC=$(DYNAMIC)); \
fi
build-cli: ## Build bifrost CLI binary
@$(ECHO) "$(GREEN)Building bifrost CLI...$(NC)"
@mkdir -p ./tmp
@cd cli && $(if $(LOCAL),,GOWORK=off) go build -ldflags "-X main.version=v0.1.1-dev" -o ../tmp/bifrost .
@$(ECHO) "$(GREEN)Built: tmp/bifrost$(NC)"
_build-with-docker: # Internal target for Docker-based cross-compilation
@$(ECHO) "$(CYAN)Using Docker for cross-compilation...$(NC)"; \
if [ "$(TARGET_OS)" = "linux" ]; then \
if [ -n "$(DYNAMIC)" ]; then \
$(ECHO) "$(CYAN)Building for $(TARGET_OS)/$(TARGET_ARCH) in Docker container with dynamic linking...$(NC)"; \
docker run --rm \
--platform linux/$(TARGET_ARCH) \
-v "$(shell pwd):/workspace" \
-w /workspace/transports/bifrost-http \
-e CGO_ENABLED=1 \
-e GOOS=$(TARGET_OS) \
-e GOARCH=$(TARGET_ARCH) \
$(if $(LOCAL),,-e GOWORK=off) \
golang:1.26.1-alpine3.23 \
sh -c "apk add --no-cache gcc musl-dev && \
go build \
-ldflags='-w -s -X main.Version=v$(VERSION)' \
-a -trimpath \
-o ../../tmp/bifrost-http \
."; \
else \
$(ECHO) "$(CYAN)Building for $(TARGET_OS)/$(TARGET_ARCH) in Docker container...$(NC)"; \
docker run --rm \
--platform linux/$(TARGET_ARCH) \
-v "$(shell pwd):/workspace" \
-w /workspace/transports/bifrost-http \
-e CGO_ENABLED=1 \
-e GOOS=$(TARGET_OS) \
-e GOARCH=$(TARGET_ARCH) \
$(if $(LOCAL),,-e GOWORK=off) \
golang:1.26.1-alpine3.23 \
sh -c "apk add --no-cache gcc musl-dev && \
go build \
-ldflags='-w -s -extldflags "-static" -X main.Version=v$(VERSION)' \
-a -trimpath \
-tags sqlite_static \
-o ../../tmp/bifrost-http \
."; \
fi; \
$(ECHO) "$(GREEN)Built: tmp/bifrost-http ($(TARGET_OS)/$(TARGET_ARCH), version: v$(VERSION))$(NC)"; \
else \
$(ECHO) "$(RED)Error: Docker cross-compilation only supports Linux targets$(NC)"; \
$(ECHO) "$(YELLOW)For $(TARGET_OS), please build on a native $(TARGET_OS) machine$(NC)"; \
exit 1; \
fi
docker-image: build-ui ## Build Docker image (LOCAL=1 to use Dockerfile.local)
@$(ECHO) "$(GREEN)Building Docker image...$(NC)"
$(eval GIT_SHA=$(shell git rev-parse --short HEAD))
$(eval DOCKERFILE=$(if $(LOCAL),transports/Dockerfile.local,transports/Dockerfile))
@docker build -f $(DOCKERFILE) -t bifrost -t bifrost:$(GIT_SHA) -t bifrost:latest .
@$(ECHO) "$(GREEN)Docker image built: bifrost, bifrost:$(GIT_SHA), bifrost:latest (using $(DOCKERFILE))$(NC)"
docker-run: ## Run Docker container (Usage: make docker-run [CONFIG=path/to/config.json or path/to/dir/])
@$(ECHO) "$(GREEN)Running Docker container...$(NC)"
@CONFIG_PATH="$(abspath $(CONFIG))"; \
if [ -n "$(CONFIG)" ]; then \
if [ -d "$$CONFIG_PATH" ]; then \
CONFIG_PATH="$$CONFIG_PATH/config.json"; \
fi; \
CONFIG_MOUNT="-v $$CONFIG_PATH:/app/data/config.json"; \
else \
CONFIG_MOUNT=""; \
fi; \
docker run -e APP_PORT=$(PORT) -e APP_HOST=0.0.0.0 -p $(PORT):$(PORT) -e LOG_LEVEL=$(LOG_LEVEL) -e LOG_STYLE=$(LOG_STYLE) -v $(shell pwd):/app/data $$CONFIG_MOUNT bifrost
docs: ## Prepare local docs
@$(ECHO) "$(GREEN)Preparing local docs...$(NC)"
@cd docs && npx --yes mintlify@latest dev
run: build ## Build and run bifrost-http (no hot reload)
@$(ECHO) "$(GREEN)Running bifrost-http...$(NC)"
@./tmp/bifrost-http \
-host "$(HOST)" \
-port "$(PORT)" \
-log-style "$(LOG_STYLE)" \
-log-level "$(LOG_LEVEL)" \
$(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)")
$(if $(APP_DIR),-app-dir "$(APP_DIR)")
run-cli: build-cli ## Run bifrost CLI (Usage: make run-cli [ARGS="--config ~/.bifrost/config.json"])
@$(ECHO) "$(GREEN)Running bifrost CLI...$(NC)"
@./tmp/bifrost $(ARGS)
clean: ## Clean build artifacts and temporary files
@$(ECHO) "$(YELLOW)Cleaning build artifacts...$(NC)"
@rm -rf tmp/
@rm -f transports/bifrost-http/build-errors.log
@rm -rf transports/bifrost-http/tmp/
@rm -rf $(TEST_REPORTS_DIR)/
@$(ECHO) "$(GREEN)Clean complete$(NC)"
clean-test-reports: ## Clean test reports only
@$(ECHO) "$(YELLOW)Cleaning test reports...$(NC)"
@rm -rf $(TEST_REPORTS_DIR)/
@$(ECHO) "$(GREEN)Test reports cleaned$(NC)"
generate-html-reports: ## Convert existing XML reports to HTML
@if ! which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(RED)Error: junit-viewer not installed$(NC)"; \
$(ECHO) "$(YELLOW)Install with: make install-junit-viewer$(NC)"; \
exit 1; \
fi
@$(ECHO) "$(GREEN)Converting XML reports to HTML...$(NC)"
@if [ ! -d "$(TEST_REPORTS_DIR)" ] || [ -z "$$(ls -A $(TEST_REPORTS_DIR)/*.xml 2>/dev/null)" ]; then \
$(ECHO) "$(YELLOW)No XML reports found in $(TEST_REPORTS_DIR)$(NC)"; \
$(ECHO) "$(YELLOW)Run tests first: make test-all$(NC)"; \
exit 0; \
fi
@for xml in $(TEST_REPORTS_DIR)/*.xml; do \
html=$${xml%.xml}.html; \
$(ECHO) " Converting $$(basename $$xml) → $$(basename $$html)"; \
junit-viewer --results=$$xml --save=$$html 2>/dev/null || true; \
done
@$(ECHO) ""
@$(ECHO) "$(GREEN)✓ HTML reports generated$(NC)"
@$(ECHO) "$(CYAN)View reports:$(NC)"
@ls -1 $(TEST_REPORTS_DIR)/*.html 2>/dev/null | sed 's|$(TEST_REPORTS_DIR)/| open $(TEST_REPORTS_DIR)/|' || true
test: install-gotestsum ## Run tests for bifrost-http
@$(ECHO) "$(GREEN)Running bifrost-http tests...$(NC)"
@mkdir -p $(TEST_REPORTS_DIR)
@cd transports/bifrost-http && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../$(TEST_REPORTS_DIR)/bifrost-http.xml \
-- -v ./...
@if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \
if junit-viewer --results=$(TEST_REPORTS_DIR)/bifrost-http.xml --save=$(TEST_REPORTS_DIR)/bifrost-http.html 2>/dev/null; then \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML report: $(TEST_REPORTS_DIR)/bifrost-http.html$(NC)"; \
$(ECHO) "$(CYAN)Open with: open $(TEST_REPORTS_DIR)/bifrost-http.html$(NC)"; \
else \
$(ECHO) "$(YELLOW)HTML generation failed. JUnit XML report available.$(NC)"; \
$(ECHO) "$(CYAN)JUnit XML report: $(TEST_REPORTS_DIR)/bifrost-http.xml$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(YELLOW)junit-viewer not installed. Install with: make install-junit-viewer$(NC)"; \
$(ECHO) "$(CYAN)JUnit XML report: $(TEST_REPORTS_DIR)/bifrost-http.xml$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $(TEST_REPORTS_DIR)/bifrost-http.xml$(NC)"; \
fi
test-core: install-gotestsum $(if $(DEBUG),install-delve) ## Run core tests (Usage: make test-core PROVIDER=openai TESTCASE=TestName or PATTERN=substring, DEBUG=1 for debugger)
@$(ECHO) "$(GREEN)Running core tests...$(NC)"
@mkdir -p $(TEST_REPORTS_DIR)
@if [ -n "$(PATTERN)" ] && [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(RED)Error: PATTERN and TESTCASE are mutually exclusive$(NC)"; \
$(ECHO) "$(YELLOW)Use PATTERN for substring matching or TESTCASE for exact match$(NC)"; \
exit 1; \
fi
@TEST_FAILED=0; \
REPORT_FILE=""; \
if [ -n "$(PROVIDER)" ]; then \
$(ECHO) "$(CYAN)Running tests for provider: $(PROVIDER)$(NC)"; \
if [ ! -f "core/providers/$(PROVIDER)/$(PROVIDER)_test.go" ]; then \
$(ECHO) "$(RED)Error: Provider test file '$(PROVIDER)_test.go' not found in core/providers/$(PROVIDER)/$(NC)"; \
$(ECHO) "$(YELLOW)Available providers:$(NC)"; \
find core/providers -name "*_test.go" -type f 2>/dev/null | sed 's|core/providers/\([^/]*\)/.*|\1|' | sort -u | sed 's/^/ - /'; \
exit 1; \
fi; \
fi; \
if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
if [ -n "$(DEBUG)" ]; then \
$(ECHO) "$(CYAN)Debug mode enabled - delve debugger will listen on port 2345$(NC)"; \
$(ECHO) "$(YELLOW)Attach your debugger to localhost:2345$(NC)"; \
fi; \
if [ -n "$(PROVIDER)" ]; then \
PROVIDER_TEST_NAME=$$($(ECHO) "$(PROVIDER)" | awk '{print toupper(substr($$0,1,1)) tolower(substr($$0,2))}' | sed 's/openai/OpenAI/i; s/sgl/SGL/i'); \
if [ -n "$(TESTCASE)" ]; then \
CLEAN_TESTCASE="$(TESTCASE)"; \
CLEAN_TESTCASE=$${CLEAN_TESTCASE#Test$${PROVIDER_TEST_NAME}/}; \
CLEAN_TESTCASE=$${CLEAN_TESTCASE#$${PROVIDER_TEST_NAME}Tests/}; \
CLEAN_TESTCASE=$$($(ECHO) "$$CLEAN_TESTCASE" | sed 's|^Test[A-Z][A-Za-z]*/[A-Z][A-Za-z]*Tests/||'); \
$(ECHO) "$(CYAN)Running Test$${PROVIDER_TEST_NAME}/$${PROVIDER_TEST_NAME}Tests/$$CLEAN_TESTCASE...$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/core-$(PROVIDER)-$$(echo $$CLEAN_TESTCASE | sed 's|/|_|g').xml"; \
if [ -n "$(DEBUG)" ]; then \
cd core/providers/$(PROVIDER) && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v -test.run "^Test$${PROVIDER_TEST_NAME}$$/.*Tests/$$CLEAN_TESTCASE$$" || TEST_FAILED=1; \
else \
cd core/providers/$(PROVIDER) && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../../$$REPORT_FILE \
-- -v -timeout 20m -run "^Test$${PROVIDER_TEST_NAME}$$/.*Tests/$$CLEAN_TESTCASE$$" || TEST_FAILED=1; \
fi; \
cd ../../..; \
$(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \
junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \
$(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
elif [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running tests matching '$(PATTERN)' for $${PROVIDER_TEST_NAME}...$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/core-$(PROVIDER)-$(PATTERN).xml"; \
if [ -n "$(DEBUG)" ]; then \
cd core/providers/$(PROVIDER) && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v -test.run ".*$(PATTERN).*" || TEST_FAILED=1; \
else \
cd core/providers/$(PROVIDER) && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../../$$REPORT_FILE \
-- -v -timeout 20m -run ".*$(PATTERN).*" || TEST_FAILED=1; \
fi; \
cd ../../..; \
$(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \
junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \
$(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) "$(CYAN)Running Test$${PROVIDER_TEST_NAME}...$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/core-$(PROVIDER).xml"; \
if [ -n "$(DEBUG)" ]; then \
cd core/providers/$(PROVIDER) && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v -test.run "^Test$${PROVIDER_TEST_NAME}$$" || TEST_FAILED=1; \
else \
cd core/providers/$(PROVIDER) && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../../$$REPORT_FILE \
-- -v -timeout 20m -run "^Test$${PROVIDER_TEST_NAME}$$" || TEST_FAILED=1; \
fi; \
cd ../../..; \
$(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \
junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \
$(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
fi; \
else \
if [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(RED)Error: TESTCASE requires PROVIDER to be specified$(NC)"; \
$(ECHO) "$(YELLOW)Usage: make test-core PROVIDER=openai TESTCASE=SpeechSynthesisStreamAdvanced/MultipleVoices_Streaming/StreamingVoice_echo$(NC)"; \
exit 1; \
fi; \
if [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running tests matching '$(PATTERN)' across all providers...$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/core-all-$(PATTERN).xml"; \
if [ -n "$(DEBUG)" ]; then \
cd core && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 ./providers/... -- -test.v -test.run ".*$(PATTERN).*" || TEST_FAILED=1; \
else \
cd core && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../$$REPORT_FILE \
-- -v -timeout 20m -run ".*$(PATTERN).*" ./providers/... || TEST_FAILED=1; \
fi; \
else \
REPORT_FILE="$(TEST_REPORTS_DIR)/core-all.xml"; \
if [ -n "$(DEBUG)" ]; then \
cd core && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 ./providers/... -- -test.v || TEST_FAILED=1; \
else \
cd core && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../$$REPORT_FILE \
-- -v ./providers/... || TEST_FAILED=1; \
fi; \
fi; \
cd ..; \
$(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \
junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \
$(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
fi; \
if [ -f "$$REPORT_FILE" ]; then \
ALL_FAILED=$$(grep -B 1 '<failure' "$$REPORT_FILE" 2>/dev/null | \
grep '<testcase' | \
sed 's/.*name="\([^"]*\)".*/\1/' | \
sort -u); \
MAX_DEPTH=$$($(ECHO) "$$ALL_FAILED" | awk -F'/' '{print NF}' | sort -n | tail -1); \
FAILED_TESTS=$$($(ECHO) "$$ALL_FAILED" | awk -F'/' -v max="$$MAX_DEPTH" 'NF == max'); \
FAILURES=$$($(ECHO) "$$FAILED_TESTS" | grep -v '^$$' | wc -l | tr -d ' '); \
if [ "$$FAILURES" -gt 0 ]; then \
$(ECHO) ""; \
$(ECHO) "$(RED)═══════════════════════════════════════════════════════════$(NC)"; \
$(ECHO) "$(RED) FAILED TEST CASES $(NC)"; \
$(ECHO) "$(RED)═══════════════════════════════════════════════════════════$(NC)"; \
$(ECHO) ""; \
printf "$(YELLOW)%-60s %-20s$(NC)\n" "Test Name" "Status"; \
printf "$(YELLOW)%-60s %-20s$(NC)\n" "─────────────────────────────────────────────────────────────" "────────────────────"; \
$(ECHO) "$$FAILED_TESTS" | while read -r testname; do \
if [ -n "$$testname" ]; then \
printf "$(RED)%-60s %-20s$(NC)\n" "$$testname" "FAILED"; \
fi; \
done; \
$(ECHO) ""; \
$(ECHO) "$(RED)Total Failures: $$FAILURES$(NC)"; \
$(ECHO) ""; \
else \
$(ECHO) ""; \
$(ECHO) "$(GREEN)═══════════════════════════════════════════════════════════$(NC)"; \
$(ECHO) "$(GREEN) ALL TESTS PASSED ✓ $(NC)"; \
$(ECHO) "$(GREEN)═══════════════════════════════════════════════════════════$(NC)"; \
$(ECHO) ""; \
fi; \
fi; \
if [ $$TEST_FAILED -eq 1 ]; then \
exit 1; \
fi
cleanup-junit-xml: ## Internal: Clean up JUnit XML to remove parent test cases with child failures
@if [ -z "$(REPORT_FILE)" ]; then \
$(ECHO) "$(RED)Error: REPORT_FILE not specified$(NC)"; \
exit 1; \
fi
@if [ ! -f "$(REPORT_FILE)" ]; then \
exit 0; \
fi
@ALL_FAILED=$$(grep -B 1 '<failure' "$(REPORT_FILE)" 2>/dev/null | \
grep '<testcase' | \
sed 's/.*name="\([^"]*\)".*/\1/' | \
sort -u); \
if [ -n "$$ALL_FAILED" ]; then \
MAX_DEPTH=$$($(ECHO) "$$ALL_FAILED" | awk -F'/' '{print NF}' | sort -n | tail -1); \
PARENT_TESTS=$$($(ECHO) "$$ALL_FAILED" | awk -F'/' -v max="$$MAX_DEPTH" 'NF < max'); \
if [ -n "$$PARENT_TESTS" ]; then \
cp "$(REPORT_FILE)" "$(REPORT_FILE).tmp"; \
$(ECHO) "$$PARENT_TESTS" | while IFS= read -r parent; do \
if [ -n "$$parent" ]; then \
ESCAPED=$$($(ECHO) "$$parent" | sed 's/[\/&]/\\&/g'); \
perl -i -pe 'BEGIN{undef $$/;} s/<testcase[^>]*name="'"$$ESCAPED"'"[^>]*>.*?<failure.*?<\/testcase>//gs' "$(REPORT_FILE).tmp" 2>/dev/null || true; \
fi; \
done; \
if [ -f "$(REPORT_FILE).tmp" ]; then \
mv "$(REPORT_FILE).tmp" "$(REPORT_FILE)"; \
fi; \
fi; \
fi
test-plugins: install-gotestsum ## Run plugin tests
@$(ECHO) "$(GREEN)Running plugin tests...$(NC)"
@mkdir -p $(TEST_REPORTS_DIR)
@if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
cd plugins && find . -name "*.go" -path "*/tests/*" -o -name "*_test.go" | head -1 > /dev/null && \
for dir in $$(find . -name "*_test.go" -exec dirname {} \; | sort -u); do \
plugin_name=$$(echo $$dir | sed 's|^\./||' | sed 's|/|-|g'); \
$(ECHO) "Testing $$dir..."; \
cd $$dir && gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../$(TEST_REPORTS_DIR)/plugin-$$plugin_name.xml \
-- -v ./... && cd - > /dev/null; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report for $$plugin_name...$(NC)"; \
junit-viewer --results=../$(TEST_REPORTS_DIR)/plugin-$$plugin_name.xml --save=../$(TEST_REPORTS_DIR)/plugin-$$plugin_name.html 2>/dev/null || true; \
fi; \
fi; \
done || $(ECHO) "No plugin tests found"
@$(ECHO) ""
@if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
$(ECHO) "$(CYAN)HTML reports saved to $(TEST_REPORTS_DIR)/plugin-*.html$(NC)"; \
else \
$(ECHO) "$(CYAN)JUnit XML reports saved to $(TEST_REPORTS_DIR)/plugin-*.xml$(NC)"; \
fi
test-framework: install-gotestsum ## Run framework tests
@$(ECHO) "$(GREEN)Running framework tests...$(NC)"
@mkdir -p $(TEST_REPORTS_DIR)
@if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
cd framework && find . -name "*.go" -path "*/tests/*" -o -name "*_test.go" | head -1 > /dev/null && \
for dir in $$(find . -name "*_test.go" -exec dirname {} \; | sort -u); do \
pkg_name=$$(echo $$dir | sed 's|^\./||' | sed 's|/|-|g'); \
$(ECHO) "Testing $$dir..."; \
cd $$dir && gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../$(TEST_REPORTS_DIR)/framework-$$pkg_name.xml \
-- -v ./... && cd - > /dev/null; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report for $$pkg_name...$(NC)"; \
junit-viewer --results=../$(TEST_REPORTS_DIR)/framework-$$pkg_name.xml --save=../$(TEST_REPORTS_DIR)/framework-$$pkg_name.html 2>/dev/null || true; \
fi; \
fi; \
done || $(ECHO) "No framework tests found"
@$(ECHO) ""
@if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
$(ECHO) "$(CYAN)HTML reports saved to $(TEST_REPORTS_DIR)/framework-*.html$(NC)"; \
else \
$(ECHO) "$(CYAN)JUnit XML reports saved to $(TEST_REPORTS_DIR)/framework-*.xml$(NC)"; \
fi
test-http-transport: install-gotestsum ## Run HTTP transport tests
@$(ECHO) "$(GREEN)Running HTTP transport tests...$(NC)"
@mkdir -p $(TEST_REPORTS_DIR)
@if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
cd transports/bifrost-http && find . -name "*.go" -path "*/tests/*" -o -name "*_test.go" | head -1 > /dev/null && \
for dir in $$(find . -name "*_test.go" -exec dirname {} \; | sort -u); do \
pkg_name=$$(echo $$dir | sed 's|^\./||' | sed 's|/|-|g'); \
$(ECHO) "Testing $$dir..."; \
cd $$dir && gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../../$(TEST_REPORTS_DIR)/http-transport-$$pkg_name.xml \
-- -v ./... && cd - > /dev/null; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report for $$pkg_name...$(NC)"; \
junit-viewer --results=../../$(TEST_REPORTS_DIR)/http-transport-$$pkg_name.xml --save=../../$(TEST_REPORTS_DIR)/http-transport-$$pkg_name.html 2>/dev/null || true; \
fi; \
fi; \
done || $(ECHO) "No HTTP transport tests found"
@$(ECHO) ""
@if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
$(ECHO) "$(CYAN)HTML reports saved to $(TEST_REPORTS_DIR)/http-transport-*.html$(NC)"; \
else \
$(ECHO) "$(CYAN)JUnit XML reports saved to $(TEST_REPORTS_DIR)/http-transport-*.xml$(NC)"; \
fi
test-governance: install-gotestsum $(if $(DEBUG),install-delve) ## Run governance tests (Usage: make test-governance TESTCASE=TestName or PATTERN=substring, DEBUG=1 for debugger)
@$(ECHO) "$(GREEN)Running governance tests...$(NC)"
@mkdir -p $(TEST_REPORTS_DIR)
@if [ -n "$(PATTERN)" ] && [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(RED)Error: PATTERN and TESTCASE are mutually exclusive$(NC)"; \
$(ECHO) "$(YELLOW)Use PATTERN for substring matching or TESTCASE for exact match$(NC)"; \
exit 1; \
fi
@if [ ! -d "tests/governance" ]; then \
$(ECHO) "$(RED)Error: Governance tests directory not found$(NC)"; \
exit 1; \
fi
@TEST_FAILED=0; \
REPORT_FILE=""; \
if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
if [ -n "$(DEBUG)" ]; then \
$(ECHO) "$(CYAN)Debug mode enabled - delve debugger will listen on port 2345$(NC)"; \
$(ECHO) "$(YELLOW)Attach your debugger to localhost:2345$(NC)"; \
fi; \
if [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(CYAN)Running test case: $(TESTCASE)$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/governance-$$(echo $(TESTCASE) | sed 's|/|_|g').xml"; \
if [ -n "$(DEBUG)" ]; then \
cd tests/governance && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v -test.run "^$(TESTCASE)$$" || TEST_FAILED=1; \
else \
cd tests/governance && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../$$REPORT_FILE \
-- -v -run "^$(TESTCASE)$$" || TEST_FAILED=1; \
fi; \
cd ../..; \
$(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \
junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \
$(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
elif [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running tests matching '$(PATTERN)'...$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/governance-$(PATTERN).xml"; \
if [ -n "$(DEBUG)" ]; then \
cd tests/governance && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v -test.run ".*$(PATTERN).*" || TEST_FAILED=1; \
else \
cd tests/governance && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../$$REPORT_FILE \
-- -v -run ".*$(PATTERN).*" || TEST_FAILED=1; \
fi; \
cd ../..; \
$(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \
junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \
$(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) "$(CYAN)Running all governance tests...$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/governance-all.xml"; \
if [ -n "$(DEBUG)" ]; then \
cd tests/governance && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v || TEST_FAILED=1; \
else \
cd tests/governance && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../$$REPORT_FILE \
-- -v || TEST_FAILED=1; \
fi; \
cd ../..; \
$(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \
junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \
$(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
fi; \
if [ -f "$$REPORT_FILE" ]; then \
ALL_FAILED=$$(grep -B 1 '<failure' "$$REPORT_FILE" 2>/dev/null | \
grep '<testcase' | \
sed 's/.*name="\([^"]*\)".*/\1/' | \
sort -u); \
MAX_DEPTH=$$($(ECHO) "$$ALL_FAILED" | awk -F'/' '{print NF}' | sort -n | tail -1); \
FAILED_TESTS=$$($(ECHO) "$$ALL_FAILED" | awk -F'/' -v max="$$MAX_DEPTH" 'NF == max'); \
FAILURES=$$($(ECHO) "$$FAILED_TESTS" | grep -v '^$$' | wc -l | tr -d ' '); \
if [ "$$FAILURES" -gt 0 ]; then \
$(ECHO) ""; \
$(ECHO) "$(RED)═══════════════════════════════════════════════════════════$(NC)"; \
$(ECHO) "$(RED) FAILED TEST CASES $(NC)"; \
$(ECHO) "$(RED)═══════════════════════════════════════════════════════════$(NC)"; \
$(ECHO) ""; \
printf "$(YELLOW)%-60s %-20s$(NC)\n" "Test Name" "Status"; \
printf "$(YELLOW)%-60s %-20s$(NC)\n" "─────────────────────────────────────────────────────────────" "────────────────────"; \
$(ECHO) "$$FAILED_TESTS" | while read -r testname; do \
if [ -n "$$testname" ]; then \
printf "$(RED)%-60s %-20s$(NC)\n" "$$testname" "FAILED"; \
fi; \
done; \
$(ECHO) ""; \
$(ECHO) "$(RED)Total Failures: $$FAILURES$(NC)"; \
$(ECHO) ""; \
else \
$(ECHO) ""; \
$(ECHO) "$(GREEN)═══════════════════════════════════════════════════════════$(NC)"; \
$(ECHO) "$(GREEN) ALL TESTS PASSED ✓ $(NC)"; \
$(ECHO) "$(GREEN)═══════════════════════════════════════════════════════════$(NC)"; \
$(ECHO) ""; \
fi; \
fi; \
if [ $$TEST_FAILED -eq 1 ]; then \
exit 1; \
fi
setup-mcp-tests: ## Build all MCP test servers in examples/mcps/ (Go and TypeScript)
@$(ECHO) "$(GREEN)Building MCP test servers...$(NC)"
@$(USE_NODE); \
FAILED=0; \
for mcp_dir in examples/mcps/*/; do \
if [ -d "$$mcp_dir" ]; then \
mcp_name=$$(basename $$mcp_dir); \
if [ -f "$$mcp_dir/go.mod" ]; then \
$(ECHO) "$(CYAN)Building $$mcp_name (Go)...$(NC)"; \
mkdir -p "$$mcp_dir/bin"; \
if cd "$$mcp_dir" && GOWORK=off go build -o bin/$$mcp_name . && cd - > /dev/null; then \
$(ECHO) "$(GREEN)$$mcp_name$(NC)"; \
else \
$(ECHO) "$(RED)$$mcp_name failed$(NC)"; \
FAILED=1; \
cd - > /dev/null 2>&1 || true; \
fi; \
elif [ -f "$$mcp_dir/package.json" ]; then \
$(ECHO) "$(CYAN)Building $$mcp_name (TypeScript)...$(NC)"; \
if cd "$$mcp_dir" && npm install --silent && npm run build && cd - > /dev/null; then \
$(ECHO) "$(GREEN)$$mcp_name$(NC)"; \
else \
$(ECHO) "$(RED)$$mcp_name failed$(NC)"; \
FAILED=1; \
cd - > /dev/null 2>&1 || true; \
fi; \
fi; \
fi; \
done; \
if [ $$FAILED -eq 1 ]; then \
$(ECHO) "$(RED)Some MCP test servers failed to build$(NC)"; \
exit 1; \
fi
@$(ECHO) ""
@$(ECHO) "$(GREEN)✓ All MCP test servers built$(NC)"
test-mcp: install-gotestsum setup-mcp-tests ## Run MCP tests (Usage: make test-mcp [TYPE=connection] [TESTCASE=TestName] [PATTERN=substring])
@$(ECHO) "$(GREEN)Running MCP tests...$(NC)"
@mkdir -p $(TEST_REPORTS_DIR)
@if [ -n "$(PATTERN)" ] && [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(RED)Error: PATTERN and TESTCASE are mutually exclusive$(NC)"; \
$(ECHO) "$(YELLOW)Use PATTERN for substring matching or TESTCASE for exact match$(NC)"; \
exit 1; \
fi
@if [ ! -d "core/internal/mcptests" ]; then \
$(ECHO) "$(RED)Error: MCP tests directory not found$(NC)"; \
exit 1; \
fi
@TEST_FAILED=0; \
REPORT_FILE=""; \
if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
if [ -n "$(TYPE)" ]; then \
TYPE_CLEAN=$$(echo $(TYPE) | sed 's/_test\.go$$//'); \
TEST_FILE="core/internal/mcptests/$${TYPE_CLEAN}_test.go"; \
if [ ! -f "$$TEST_FILE" ]; then \
$(ECHO) "$(RED)Error: Test file '$$TEST_FILE' not found$(NC)"; \
$(ECHO) "$(YELLOW)Available test types:$(NC)"; \
ls -1 core/internal/mcptests/*_test.go 2>/dev/null | sed 's|core/internal/mcptests/||' | sed 's|_test\.go$$||' | sed 's/^/ - /'; \
exit 1; \
fi; \
TEST_PATTERN=$$(grep -h "^func Test" $$TEST_FILE 2>/dev/null | sed 's/func \(Test[^(]*\).*/\1/' | paste -sd '|' - || $(ECHO) "^Test"); \
if [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(CYAN)Running $(TYPE) test: $(TESTCASE)...$(NC)"; \
SAFE_TESTCASE=$$($(ECHO) "$(TESTCASE)" | sed 's|/|_|g'); \
REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-$(TYPE)-$$SAFE_TESTCASE.xml"; \
cd core/internal/mcptests && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../../$$REPORT_FILE \
-- -v -race -run "^$(TESTCASE)$$" . || TEST_FAILED=1; \
elif [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running $(TYPE) tests matching '$(PATTERN)'...$(NC)"; \
SAFE_PATTERN=$$($(ECHO) "$(PATTERN)" | sed 's|/|_|g'); \
REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-$(TYPE)-$$SAFE_PATTERN.xml"; \
cd core/internal/mcptests && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../../$$REPORT_FILE \
-- -v -race -run ".*$(PATTERN).*" . || TEST_FAILED=1; \
else \
$(ECHO) "$(CYAN)Running all $(TYPE) tests (pattern: $$TEST_PATTERN)...$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-$(TYPE).xml"; \
cd core/internal/mcptests && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../../$$REPORT_FILE \
-- -v -race -run "$$TEST_PATTERN" . || TEST_FAILED=1; \
fi; \
cd ../../..; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \
junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \
$(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
if [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(CYAN)Running test case: $(TESTCASE) across all MCP tests...$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-all-$(TESTCASE).xml"; \
cd core/internal/mcptests && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../../$$REPORT_FILE \
-- -v -race -run "^$(TESTCASE)$$" || TEST_FAILED=1; \
elif [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running tests matching '$(PATTERN)' across all MCP tests...$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-all-$(PATTERN).xml"; \
cd core/internal/mcptests && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../../$$REPORT_FILE \
-- -v -race -run ".*$(PATTERN).*" || TEST_FAILED=1; \
else \
$(ECHO) "$(CYAN)Running all MCP tests...$(NC)"; \
REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-all.xml"; \
cd core/internal/mcptests && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../../../$$REPORT_FILE \
-- -v -race || TEST_FAILED=1; \
fi; \
cd ../../..; \
if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
if which junit-viewer > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \
junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \
$(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
else \
$(ECHO) ""; \
$(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \
fi; \
fi; \
if [ $$TEST_FAILED -eq 1 ]; then \
exit 1; \
fi
test-all: test-core test-framework test-plugins test-http-transport test test-cli ## Run all tests
@$(ECHO) ""
@$(ECHO) "$(GREEN)═══════════════════════════════════════════════════════════$(NC)"
@$(ECHO) "$(GREEN) All Tests Complete - Summary $(NC)"
@$(ECHO) "$(GREEN)═══════════════════════════════════════════════════════════$(NC)"
@$(ECHO) ""
@if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \
$(ECHO) "$(YELLOW)Generating combined HTML report...$(NC)"; \
junit-viewer --results=$(TEST_REPORTS_DIR) --save=$(TEST_REPORTS_DIR)/index.html 2>/dev/null || true; \
$(ECHO) ""; \
$(ECHO) "$(CYAN)HTML reports available in $(TEST_REPORTS_DIR)/:$(NC)"; \
ls -1 $(TEST_REPORTS_DIR)/*.html 2>/dev/null | sed 's/^/ ✓ /' || $(ECHO) " No reports found"; \
$(ECHO) ""; \
$(ECHO) "$(YELLOW)📊 View all test results:$(NC)"; \
$(ECHO) "$(CYAN) open $(TEST_REPORTS_DIR)/index.html$(NC)"; \
$(ECHO) ""; \
$(ECHO) "$(YELLOW)Or view individual reports:$(NC)"; \
ls -1 $(TEST_REPORTS_DIR)/*.html 2>/dev/null | grep -v index.html | sed 's|$(TEST_REPORTS_DIR)/| open $(TEST_REPORTS_DIR)/|' || true; \
$(ECHO) ""; \
else \
$(ECHO) "$(CYAN)JUnit XML reports available in $(TEST_REPORTS_DIR)/:$(NC)"; \
ls -1 $(TEST_REPORTS_DIR)/*.xml 2>/dev/null | sed 's/^/ ✓ /' || $(ECHO) " No reports found"; \
$(ECHO) ""; \
fi
test-chatbot: ## Run interactive chatbot integration test (Usage: RUN_CHATBOT_TEST=1 make test-chatbot)
@$(ECHO) "$(GREEN)Running interactive chatbot integration test...$(NC)"
@if [ -z "$(RUN_CHATBOT_TEST)" ]; then \
$(ECHO) "$(YELLOW)⚠️ This is an interactive test. Set RUN_CHATBOT_TEST=1 to run it.$(NC)"; \
$(ECHO) "$(CYAN)Usage: RUN_CHATBOT_TEST=1 make test-chatbot$(NC)"; \
$(ECHO) ""; \
$(ECHO) "$(YELLOW)Required environment variables:$(NC)"; \
$(ECHO) " - OPENAI_API_KEY (required)"; \
$(ECHO) " - ANTHROPIC_API_KEY (optional)"; \
$(ECHO) " - Additional provider keys as needed"; \
exit 0; \
fi
@if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi
@cd core && RUN_CHATBOT_TEST=1 go test -v -run TestChatbot
test-integrations-py: ## Run Python integration tests (Usage: make test-integrations-py [INTEGRATION=openai] [TESTCASE=test_name] [PATTERN=substring] [VERBOSE=1])
@$(ECHO) "$(GREEN)Running Python integration tests...$(NC)"
@if [ ! -d "tests/integrations/python" ]; then \
$(ECHO) "$(RED)Error: tests/integrations/python directory not found$(NC)"; \
exit 1; \
fi; \
if [ -n "$(PATTERN)" ] && [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(RED)Error: PATTERN and TESTCASE are mutually exclusive$(NC)"; \
$(ECHO) "$(YELLOW)Use PATTERN for substring matching or TESTCASE for exact match$(NC)"; \
exit 1; \
fi; \
if [ -n "$(TESTCASE)" ] && [ -z "$(INTEGRATION)" ]; then \
$(ECHO) "$(RED)Error: TESTCASE requires INTEGRATION to be specified$(NC)"; \
$(ECHO) "$(YELLOW)Usage: make test-integrations-py INTEGRATION=anthropic TESTCASE=test_05_end2end_tool_calling$(NC)"; \
exit 1; \
fi; \
if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
BIFROST_STARTED=0; \
BIFROST_PID=""; \
TAIL_PID=""; \
TEST_PORT=$${PORT:-8080}; \
TEST_HOST=$${HOST:-localhost}; \
$(ECHO) "$(CYAN)Checking if Bifrost is running on $$TEST_HOST:$$TEST_PORT...$(NC)"; \
if curl -s -o /dev/null -w "%{http_code}" http://$$TEST_HOST:$$TEST_PORT/health 2>/dev/null | grep -q "200\|404"; then \
$(ECHO) "$(GREEN)✓ Bifrost is already running$(NC)"; \
else \
$(ECHO) "$(YELLOW)Bifrost not running, starting it...$(NC)"; \
./tmp/bifrost-http -host "$$TEST_HOST" -port "$$TEST_PORT" -log-style "$(LOG_STYLE)" -log-level "$(LOG_LEVEL)" -app-dir tests/integrations/python > /tmp/bifrost-test.log 2>&1 & \
BIFROST_PID=$$!; \
BIFROST_STARTED=1; \
$(ECHO) "$(YELLOW)Waiting for Bifrost to be ready...$(NC)"; \
$(ECHO) "$(CYAN)Bifrost logs: /tmp/bifrost-test.log$(NC)"; \
(tail -f /tmp/bifrost-test.log 2>/dev/null | grep -E "error|panic|Error|ERRO|fatal|Fatal|FATAL" --line-buffered &) & \
TAIL_PID=$$!; \
for i in 1 2 3 4 5 6 7 8 9 10; do \
if curl -s -o /dev/null http://$$TEST_HOST:$$TEST_PORT/health 2>/dev/null; then \
$(ECHO) "$(GREEN)✓ Bifrost is ready (PID: $$BIFROST_PID)$(NC)"; \
break; \
fi; \
if [ $$i -eq 10 ]; then \
$(ECHO) "$(RED)Failed to start Bifrost$(NC)"; \
$(ECHO) "$(YELLOW)Bifrost logs:$(NC)"; \
cat /tmp/bifrost-test.log 2>/dev/null || $(ECHO) "No log file found"; \
[ -n "$$BIFROST_PID" ] && kill $$BIFROST_PID 2>/dev/null; \
[ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null; \
exit 1; \
fi; \
sleep 1; \
done; \
fi; \
TEST_FAILED=0; \
if ! which uv > /dev/null 2>&1; then \
$(ECHO) "$(YELLOW)uv not found, checking for pytest...$(NC)"; \
if ! which pytest > /dev/null 2>&1; then \
$(ECHO) "$(RED)Error: Neither uv nor pytest found$(NC)"; \
$(ECHO) "$(YELLOW)Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh$(NC)"; \
$(ECHO) "$(YELLOW)Or install pytest: pip install pytest$(NC)"; \
[ $$BIFROST_STARTED -eq 1 ] && [ -n "$$BIFROST_PID" ] && kill $$BIFROST_PID 2>/dev/null; \
[ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null; \
exit 1; \
fi; \
$(ECHO) "$(CYAN)Using pytest directly$(NC)"; \
if [ -n "$(INTEGRATION)" ]; then \
if [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(CYAN)Running $(INTEGRATION) integration test: $(TESTCASE)...$(NC)"; \
cd tests/integrations/python && pytest tests/test_$(INTEGRATION).py::$(TESTCASE) $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \
elif [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests matching '$(PATTERN)'...$(NC)"; \
cd tests/integrations/python && pytest tests/test_$(INTEGRATION).py -k "$(PATTERN)" $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \
else \
$(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests...$(NC)"; \
cd tests/integrations/python && pytest tests/test_$(INTEGRATION).py $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \
fi; \
else \
if [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running all integration tests matching '$(PATTERN)'...$(NC)"; \
cd tests/integrations/python && pytest -k "$(PATTERN)" $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \
else \
$(ECHO) "$(CYAN)Running all integration tests...$(NC)"; \
cd tests/integrations/python && pytest $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \
fi; \
fi; \
else \
$(ECHO) "$(CYAN)Using uv (fast mode)$(NC)"; \
cd tests/integrations/python && \
if [ -n "$(INTEGRATION)" ]; then \
if [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(CYAN)Running $(INTEGRATION) integration test: $(TESTCASE)...$(NC)"; \
uv run pytest tests/test_$(INTEGRATION).py::$(TESTCASE) $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \
elif [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests matching '$(PATTERN)'...$(NC)"; \
uv run pytest tests/test_$(INTEGRATION).py -k "$(PATTERN)" $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \
else \
$(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests...$(NC)"; \
uv run pytest tests/test_$(INTEGRATION).py $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \
fi; \
else \
if [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running all integration tests matching '$(PATTERN)'...$(NC)"; \
uv run pytest -k "$(PATTERN)" $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \
else \
$(ECHO) "$(CYAN)Running all integration tests...$(NC)"; \
uv run pytest $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \
fi; \
fi; \
fi; \
if [ $$BIFROST_STARTED -eq 1 ] && [ -n "$$BIFROST_PID" ]; then \
$(ECHO) "$(YELLOW)Stopping Bifrost (PID: $$BIFROST_PID)...$(NC)"; \
kill $$BIFROST_PID 2>/dev/null || true; \
[ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null || true; \
wait $$BIFROST_PID 2>/dev/null || true; \
$(ECHO) "$(GREEN)✓ Bifrost stopped$(NC)"; \
if [ $$TEST_FAILED -eq 1 ]; then \
$(ECHO) ""; \
$(ECHO) "$(YELLOW)Last 50 lines of Bifrost logs:$(NC)"; \
tail -50 /tmp/bifrost-test.log 2>/dev/null || $(ECHO) "No log file found"; \
fi; \
fi; \
$(ECHO) ""; \
if [ $$TEST_FAILED -eq 1 ]; then \
$(ECHO) "$(RED)✗ Integration tests failed$(NC)"; \
$(ECHO) "$(CYAN)Full Bifrost logs: /tmp/bifrost-test.log$(NC)"; \
exit 1; \
else \
$(ECHO) "$(GREEN)✓ Integration tests complete$(NC)"; \
fi
test-integrations-ts: ## Run TypeScript integration tests (Usage: make test-integrations-ts [INTEGRATION=openai] [TESTCASE=test_name] [PATTERN=substring] [VERBOSE=1])
@$(ECHO) "$(GREEN)Running TypeScript integration tests...$(NC)"
@if [ ! -d "tests/integrations/typescript" ]; then \
$(ECHO) "$(RED)Error: tests/integrations/typescript directory not found$(NC)"; \
exit 1; \
fi; \
if [ -n "$(PATTERN)" ] && [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(RED)Error: PATTERN and TESTCASE are mutually exclusive$(NC)"; \
$(ECHO) "$(YELLOW)Use PATTERN for substring matching or TESTCASE for exact match$(NC)"; \
exit 1; \
fi; \
if [ -n "$(TESTCASE)" ] && [ -z "$(INTEGRATION)" ]; then \
$(ECHO) "$(RED)Error: TESTCASE requires INTEGRATION to be specified$(NC)"; \
$(ECHO) "$(YELLOW)Usage: make test-integrations-ts INTEGRATION=openai TESTCASE=test_simple_chat$(NC)"; \
exit 1; \
fi; \
if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
BIFROST_STARTED=0; \
BIFROST_PID=""; \
TAIL_PID=""; \
TEST_PORT=$${PORT:-8080}; \
TEST_HOST=$${HOST:-localhost}; \
$(ECHO) "$(CYAN)Checking if Bifrost is running on $$TEST_HOST:$$TEST_PORT...$(NC)"; \
if curl -s -o /dev/null -w "%{http_code}" http://$$TEST_HOST:$$TEST_PORT/health 2>/dev/null | grep -q "200\|404"; then \
$(ECHO) "$(GREEN)✓ Bifrost is already running$(NC)"; \
else \
$(ECHO) "$(YELLOW)Bifrost not running, starting it...$(NC)"; \
./tmp/bifrost-http -host "$$TEST_HOST" -port "$$TEST_PORT" -log-style "$(LOG_STYLE)" -log-level "$(LOG_LEVEL)" -app-dir tests/integrations/typescript > /tmp/bifrost-test.log 2>&1 & \
BIFROST_PID=$$!; \
BIFROST_STARTED=1; \
$(ECHO) "$(YELLOW)Waiting for Bifrost to be ready...$(NC)"; \
$(ECHO) "$(CYAN)Bifrost logs: /tmp/bifrost-test.log$(NC)"; \
(tail -f /tmp/bifrost-test.log 2>/dev/null | grep -E "error|panic|Error|ERRO|fatal|Fatal|FATAL" --line-buffered &) & \
TAIL_PID=$$!; \
for i in 1 2 3 4 5 6 7 8 9 10; do \
if curl -s -o /dev/null http://$$TEST_HOST:$$TEST_PORT/health 2>/dev/null; then \
$(ECHO) "$(GREEN)✓ Bifrost is ready (PID: $$BIFROST_PID)$(NC)"; \
break; \
fi; \
if [ $$i -eq 10 ]; then \
$(ECHO) "$(RED)Failed to start Bifrost$(NC)"; \
$(ECHO) "$(YELLOW)Bifrost logs:$(NC)"; \
cat /tmp/bifrost-test.log 2>/dev/null || $(ECHO) "No log file found"; \
[ -n "$$BIFROST_PID" ] && kill $$BIFROST_PID 2>/dev/null; \
[ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null; \
exit 1; \
fi; \
sleep 1; \
done; \
fi; \
TEST_FAILED=0; \
$(USE_NODE); \
if ! which npm > /dev/null 2>&1; then \
$(ECHO) "$(RED)Error: npm not found$(NC)"; \
$(ECHO) "$(YELLOW)Install Node.js: https://nodejs.org/$(NC)"; \
[ $$BIFROST_STARTED -eq 1 ] && [ -n "$$BIFROST_PID" ] && kill $$BIFROST_PID 2>/dev/null; \
[ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null; \
exit 1; \
fi; \
$(ECHO) "$(CYAN)Using npm$(NC)"; \
cd tests/integrations/typescript && \
if [ ! -d "node_modules" ]; then \
$(ECHO) "$(YELLOW)Installing dependencies...$(NC)"; \
npm install; \
fi; \
if [ -n "$(INTEGRATION)" ]; then \
if [ -n "$(TESTCASE)" ]; then \
$(ECHO) "$(CYAN)Running $(INTEGRATION) integration test: $(TESTCASE)...$(NC)"; \
npm test -- tests/test-$(INTEGRATION).test.ts -t "$(TESTCASE)" $(if $(VERBOSE),--reporter=verbose,) || TEST_FAILED=1; \
elif [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests matching '$(PATTERN)'...$(NC)"; \
npm test -- tests/test-$(INTEGRATION).test.ts -t "$(PATTERN)" $(if $(VERBOSE),--reporter=verbose,) || TEST_FAILED=1; \
else \
$(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests...$(NC)"; \
npm test -- tests/test-$(INTEGRATION).test.ts $(if $(VERBOSE),--reporter=verbose,) || TEST_FAILED=1; \
fi; \
else \
if [ -n "$(PATTERN)" ]; then \
$(ECHO) "$(CYAN)Running all integration tests matching '$(PATTERN)'...$(NC)"; \
npm test -- -t "$(PATTERN)" $(if $(VERBOSE),--reporter=verbose,) || TEST_FAILED=1; \
else \
$(ECHO) "$(CYAN)Running all integration tests...$(NC)"; \
npm test $(if $(VERBOSE),-- --reporter=verbose,) || TEST_FAILED=1; \
fi; \
fi; \
if [ $$BIFROST_STARTED -eq 1 ] && [ -n "$$BIFROST_PID" ]; then \
$(ECHO) "$(YELLOW)Stopping Bifrost (PID: $$BIFROST_PID)...$(NC)"; \
kill $$BIFROST_PID 2>/dev/null || true; \
[ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null || true; \
wait $$BIFROST_PID 2>/dev/null || true; \
$(ECHO) "$(GREEN)✓ Bifrost stopped$(NC)"; \
if [ $$TEST_FAILED -eq 1 ]; then \
$(ECHO) ""; \
$(ECHO) "$(YELLOW)Last 50 lines of Bifrost logs:$(NC)"; \
tail -50 /tmp/bifrost-test.log 2>/dev/null || $(ECHO) "No log file found"; \
fi; \
fi; \
$(ECHO) ""; \
if [ $$TEST_FAILED -eq 1 ]; then \
$(ECHO) "$(RED)✗ TypeScript integration tests failed$(NC)"; \
$(ECHO) "$(CYAN)Full Bifrost logs: /tmp/bifrost-test.log$(NC)"; \
exit 1; \
else \
$(ECHO) "$(GREEN)✓ TypeScript integration tests complete$(NC)"; \
fi
install-playwright: ## Install Playwright test dependencies
@$(ECHO) "$(GREEN)Installing Playwright dependencies...$(NC)"
@which node > /dev/null || ($(ECHO) "$(RED)Error: Node.js is not installed. Please install Node.js first.$(NC)" && exit 1)
@which npm > /dev/null || ($(ECHO) "$(RED)Error: npm is not installed. Please install npm first.$(NC)" && exit 1)
@$(USE_NODE); cd tests/e2e && npm ci
@cd tests/e2e && if npx playwright install --list 2>/dev/null | grep -q "chromium"; then \
$(ECHO) "$(CYAN)Chromium is already installed, skipping download$(NC)"; \
else \
$(ECHO) "$(CYAN)Installing Chromium...$(NC)"; \
npx playwright install --with-deps chromium; \
fi
@$(ECHO) "$(GREEN)Playwright is ready$(NC)"
build-test-plugin: ## Build test plugin for E2E tests (copies to tmp/bifrost-test-plugin.so)
@$(ECHO) "$(GREEN)Building test plugin for E2E tests...$(NC)"
@cd examples/plugins/hello-world && make dev
@mkdir -p tmp
@cp examples/plugins/hello-world/build/hello-world.so tmp/bifrost-test-plugin.so
@$(ECHO) "$(GREEN)✓ Test plugin ready at tmp/bifrost-test-plugin.so$(NC)"
run-e2e: install-playwright ## Run E2E tests (Usage: make run-e2e [FLOW=providers|virtual-keys|config])
@$(ECHO) "$(GREEN)Running Playwright E2E tests...$(NC)"
@if [ -n "$(FLOW)" ]; then \
$(ECHO) "$(CYAN)Running $(FLOW) tests...$(NC)"; \
if [ "$(FLOW)" = "config" ]; then \
cd tests/e2e && npx playwright test --project=chromium-config; \
else \
cd tests/e2e && npx playwright test features/$(FLOW); \
fi; \
else \
$(ECHO) "$(CYAN)Running all E2E tests...$(NC)"; \
cd tests/e2e && npx playwright test; \
fi
@$(ECHO) ""
@$(ECHO) "$(GREEN)E2E tests complete$(NC)"
@$(ECHO) "$(CYAN)View HTML report: cd tests/e2e && npx playwright show-report$(NC)"
run-e2e-ui: install-playwright ## Run E2E tests in interactive UI mode
@if [ -f .env ]; then \
$(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \
set -a; . ./.env; set +a; \
fi; \
$(ECHO) "$(GREEN)Opening Playwright UI...$(NC)"; \
cd tests/e2e && npx playwright test --ui
run-e2e-headed: install-playwright ## Run E2E tests in headed browser mode
@$(ECHO) "$(GREEN)Running E2E tests in headed mode...$(NC)"
@if [ -n "$(FLOW)" ]; then \
$(ECHO) "$(CYAN)Running $(FLOW) tests (headed)...$(NC)"; \
if [ "$(FLOW)" = "config" ]; then \
cd tests/e2e && npx playwright test --project=chromium-config --headed; \
else \
cd tests/e2e && npx playwright test features/$(FLOW) --headed; \
fi; \
else \
$(ECHO) "$(CYAN)Running all E2E tests (headed)...$(NC)"; \
cd tests/e2e && npx playwright test --headed; \
fi
# Quick start with example config
quick-start: ## Quick start with example config and maxim plugin
@$(ECHO) "$(GREEN)Quick starting Bifrost with example configuration...$(NC)"
@$(MAKE) dev
# Linting and formatting
lint: ## Run linter for Go code
@$(ECHO) "$(GREEN)Running golangci-lint...$(NC)"
@golangci-lint run ./...
fmt: ## Format Go code
@$(ECHO) "$(GREEN)Formatting Go code...$(NC)"
@gofmt -s -w .
@goimports -w .
format: ## Format code (Usage: make format ui)
ifeq (ui,$(filter ui,$(MAKECMDGOALS)))
@$(ECHO) "$(GREEN)Formatting UI code...$(NC)"
@cd ui && $(USE_NODE); npm run format
else
@$(ECHO) "$(YELLOW)Usage: make format ui$(NC)"
endif
ui:
@:
# Workspace helpers
setup-workspace: ## Set up Go workspace with all local modules for development
@$(ECHO) "$(GREEN)Setting up Go workspace for local development...$(NC)"
@$(ECHO) "$(YELLOW)Cleaning existing workspace...$(NC)"
@rm -f go.work go.work.sum || true
@$(ECHO) "$(YELLOW)Initializing new workspace...$(NC)"
@go work init ./cli ./core ./framework ./transports
@$(ECHO) "$(YELLOW)Adding plugin modules...$(NC)"
@for plugin_dir in ./plugins/*/; do \
if [ -d "$$plugin_dir" ] && [ -f "$$plugin_dir/go.mod" ]; then \
$(ECHO) " Adding plugin: $$(basename $$plugin_dir)"; \
go work use "$$plugin_dir"; \
fi; \
done
@$(ECHO) "$(YELLOW)Syncing workspace...$(NC)"
@go work sync
@$(ECHO) "$(GREEN)✓ Go workspace ready with all local modules$(NC)"
@$(ECHO) ""
@$(ECHO) "$(CYAN)Local modules in workspace:$(NC)"
@go list -m all | grep "github.com/maximhq/bifrost" | grep -v " v" | sed 's/^/ ✓ /'
@$(ECHO) ""
@$(ECHO) "$(CYAN)Remote modules (no local version):$(NC)"
@go list -m all | grep "github.com/maximhq/bifrost" | grep " v" | sed 's/^/ → /'
@$(ECHO) ""
@$(ECHO) "$(YELLOW)Note: go.work files are not committed to version control$(NC)"
work-init: ## Create local go.work to use local modules for development (legacy)
@$(ECHO) "$(YELLOW)⚠️ work-init is deprecated, use 'make setup-workspace' instead$(NC)"
@$(MAKE) setup-workspace
work-clean: ## Remove local go.work
@rm -f go.work go.work.sum || true
@$(ECHO) "$(GREEN)Removed local go.work files$(NC)"
# Module parameter for mod-tidy (all/core/plugins/framework/transport)
MODULE ?= all
mod-tidy: ## Run go mod tidy on modules (Usage: make mod-tidy [MODULE=all|cli|core|plugins|framework|transport])
@$(ECHO) "$(GREEN)Running go mod tidy...$(NC)"
@if [ "$(MODULE)" = "all" ] || [ "$(MODULE)" = "cli" ]; then \
$(ECHO) "$(CYAN)Tidying cli...$(NC)"; \
cd cli && $(if $(LOCAL),,GOWORK=off) go mod tidy && $(ECHO) "$(GREEN) ✓ cli$(NC)"; \
fi
@if [ "$(MODULE)" = "all" ] || [ "$(MODULE)" = "core" ]; then \
$(ECHO) "$(CYAN)Tidying core...$(NC)"; \
cd core && go mod tidy && $(ECHO) "$(GREEN) ✓ core$(NC)"; \
fi
@if [ "$(MODULE)" = "all" ] || [ "$(MODULE)" = "framework" ]; then \
$(ECHO) "$(CYAN)Tidying framework...$(NC)"; \
cd framework && go mod tidy && $(ECHO) "$(GREEN) ✓ framework$(NC)"; \
fi
@if [ "$(MODULE)" = "all" ] || [ "$(MODULE)" = "transport" ]; then \
$(ECHO) "$(CYAN)Tidying transports...$(NC)"; \
cd transports && go mod tidy && $(ECHO) "$(GREEN) ✓ transports$(NC)"; \
fi
@if [ "$(MODULE)" = "all" ] || [ "$(MODULE)" = "plugins" ]; then \
$(ECHO) "$(CYAN)Tidying plugins...$(NC)"; \
for plugin_dir in ./plugins/*/; do \
if [ -d "$$plugin_dir" ] && [ -f "$$plugin_dir/go.mod" ]; then \
plugin_name=$$(basename $$plugin_dir); \
cd $$plugin_dir && go mod tidy && cd ../.. && $(ECHO) "$(GREEN) ✓ plugins/$$plugin_name$(NC)"; \
fi; \
done; \
fi
@$(ECHO) ""
@$(ECHO) "$(GREEN)✓ go mod tidy complete$(NC)"
test-cli: install-gotestsum ## Run CLI tests
@$(ECHO) "$(GREEN)Running CLI tests...$(NC)"
@mkdir -p $(TEST_REPORTS_DIR)
@cd cli && GOWORK=off gotestsum \
--format=$(GOTESTSUM_FORMAT) \
--junitfile=../$(TEST_REPORTS_DIR)/cli.xml \
-- ./...