diff --git a/.gitignore b/.gitignore index e1740a52822..248248ed6cb 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ systest/bulk_live/live/**/*.rdf systest/bulk_live/live/**/*.txt x/log_test/*.enc *.buf +.osgrep +.worktrees/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index afaab682bdd..70da1eab9d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -137,23 +137,28 @@ then use this local image to test Dgraph in your local Docker setup. ### Testing -Dgraph employs a ~~complex~~ sophisticated testing framework that includes extensive test coverage. -Due to the comprehensive nature of these tests, a complete test run can take several hours, -depending on your hardware. To manage this complex testing process efficiently, we've developed a -custom test framework implemented in Go, which resides in the [./t](/t) directory. This specialized -framework provides enhanced control and flexibility beyond what's available through standard Go -testing framework. - -For dependencies, runner flags and instructions for running tests on non-Linux machines, see the -[README](t/README.md) in the [_t_](t) folder. - -Other integration tests do not use the testing framework located in the `t` folder. Consult the -[github workflow definitions](.github/workflows) folder to discover the tests we run as part of our -continuous integration process. - -Non-integration unit tests exist for many core packages that can be exercised without invoking the -testing framework. For instance, to unit test the core DQL parsing package: -`go test github.com/dgraph-io/dgraph/v25/dql`. +Dgraph employs a ~~complex~~ sophisticated testing framework with extensive test coverage. A full +test run can take several hours. We've developed a custom test runner in Go in the [t/](t) +directory, providing control and flexibility beyond the standard Go testing framework. + +The simplest way to run tests is via Make: + +```bash +# Run all tests +make test + +# Run specific test types +make test-unit # Unit tests only (no Docker) +make test-integration2 # Integration2 tests via dgraphtest +make test-upgrade # Upgrade tests + +# Use variables for more control +make test TAGS=integration2 PKG=systest/vector +``` + +Run `make help` to see all available targets and variables. + +For a comprehensive testing guide, see [TESTING.md](TESTING.md). ## Contributing diff --git a/Makefile b/Makefile index 6bffaf455dc..196f2c07426 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,21 @@ BUILD_DATE ?= $(shell git log -1 --format=%ci) BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) BUILD_VERSION ?= $(shell git describe --always --tags) -GOPATH ?= $(shell go env GOPATH) +export GOPATH ?= $(shell go env GOPATH) +GOHOSTOS := $(shell go env GOHOSTOS) +GOHOSTARCH := $(shell go env GOHOSTARCH) + +# Guard against empty GOPATH, which would resolve paths to root (e.g. /bin) +ifeq ($(GOPATH),) + $(error GOPATH is not set. Please set it explicitly, e.g. export GOPATH=$$HOME/go) +endif + +# On non-Linux systems, use a separate directory for Linux binaries +ifeq ($(GOHOSTOS),linux) + export LINUX_GOBIN ?= $(GOPATH)/bin +else + export LINUX_GOBIN ?= $(GOPATH)/linux_$(GOHOSTARCH) +endif ###################### # Build & Release Parameters @@ -20,10 +34,10 @@ GOPATH ?= $(shell go env GOPATH) DGRAPH_VERSION ?= local .PHONY: all -all: dgraph +all: dgraph ## Build all targets .PHONY: dgraph -dgraph: +dgraph: ## Build dgraph binary $(MAKE) -w -C $@ all .PHONY: dgraph-coverage @@ -31,7 +45,7 @@ dgraph-coverage: $(MAKE) -w -C dgraph test-coverage-binary .PHONY: version -version: +version: ## Show build version info @echo Dgraph: ${BUILD_VERSION} @echo Build: ${BUILD} @echo Codename: ${BUILD_CODENAME} @@ -40,30 +54,115 @@ version: @echo Go version: $(shell go version) .PHONY: install -install: - @echo "Installing Dgraph..."; \ - $(MAKE) -C dgraph install; \ +install: ## Install dgraph binary + @echo "Installing dgraph ($(GOHOSTOS)/$(GOHOSTARCH))..." + @$(MAKE) -C dgraph install +ifneq ($(GOHOSTOS),linux) + @mkdir -p $(LINUX_GOBIN) + @echo "Installing dgraph (linux/$(GOHOSTARCH))..." + @GOOS=linux GOARCH=$(GOHOSTARCH) $(MAKE) -C dgraph dgraph + @mv dgraph/dgraph $(LINUX_GOBIN)/dgraph + @echo "Installed dgraph (linux/$(GOHOSTARCH)) to $(LINUX_GOBIN)/dgraph" +endif + .PHONY: uninstall -uninstall: +uninstall: ## Uninstall dgraph binary @echo "Uninstalling Dgraph ..."; \ $(MAKE) -C dgraph uninstall; \ -.PHONY: test -test: docker-image - @mv dgraph/dgraph ${GOPATH}/bin/dgraph - @$(MAKE) -C t test +.PHONY: dgraph-installed +dgraph-installed: + @if [ ! -f "$(GOPATH)/bin/dgraph" ] || [ ! -f "$(LINUX_GOBIN)/dgraph" ]; then \ + echo "Dgraph binary missing, running make install..."; \ + $(MAKE) install; \ + fi -.PHONY: image-local local-image -image-local local-image: +.PHONY: test +test: dgraph-installed local-image ## Run tests (see 'make help' for options) +ifdef TAGS + @echo "Running tests with tags: $(TAGS)" + go test -v --tags="$(TAGS)" \ + $(if $(TEST),--run="$(TEST)") \ + $(if $(PKG),./$(PKG)/...,./...) +else ifdef FUZZ + @echo "Discovering and running fuzz tests..." +ifdef PKG + go test -v -fuzz=Fuzz -fuzztime=$(or $(FUZZTIME),300s) ./$(PKG)/... +else + @grep -r "^func Fuzz" --include="*_test.go" -l . 2>/dev/null | \ + xargs -I{} dirname {} | sort -u | while read dir; do \ + echo "Fuzzing $$dir..."; \ + go test -v -fuzz=Fuzz -fuzztime=$(or $(FUZZTIME),300s) ./$$dir/...; \ + done +endif +else + @echo "Running test suite: $(or $(SUITE),all)" + $(MAKE) -C t test args="--suite=$(or $(SUITE),all) $(if $(PKG),--pkg=\"$(PKG)\") $(if $(TEST),--test=\"$(TEST)\")" +endif + +.PHONY: test-all +test-all: ## All test suites via t/ runner (i.e. 'make test SUITE=all') + @SUITE=all $(MAKE) test + +.PHONY: test-unit +test-unit: ## Unit tests, no Docker (i.e. 'make test SUITE=unit') + @SUITE=unit $(MAKE) test + +.PHONY: test-core +test-core: ## Core tests (i.e. 'make test SUITE=core') + @SUITE=core $(MAKE) test + +.PHONY: test-integration +test-integration: ## Integration tests (i.e. 'make test TAGS=integration') + @TAGS=integration $(MAKE) test + +.PHONY: test-integration2 +test-integration2: ## Integration2 tests via dgraphtest (i.e. 'make test TAGS=integration2') + @TAGS=integration2 $(MAKE) test + +.PHONY: test-upgrade +test-upgrade: ## Upgrade tests (i.e. 'make test TAGS=upgrade') + @TAGS=upgrade $(MAKE) test + +.PHONY: test-systest +test-systest: ## System integration tests (i.e. 'make test SUITE=systest') + @SUITE=systest $(MAKE) test + +.PHONY: test-vector +test-vector: ## Vector search tests (i.e. 'make test SUITE=vector') + @SUITE=vector $(MAKE) test + +.PHONY: test-fuzz +test-fuzz: ## Fuzz tests, auto-discovers packages (i.e. 'make test FUZZ=1') + @FUZZ=1 $(MAKE) test + +.PHONY: test-ldbc +test-ldbc: ## LDBC benchmark tests (i.e. 'make test SUITE=ldbc') + @SUITE=ldbc $(MAKE) test + +.PHONY: test-load +test-load: ## Heavy load tests (i.e. 'make test SUITE=load') + @SUITE=load $(MAKE) test + +.PHONY: test-benchmark +test-benchmark: ## Go benchmarks (i.e. 'go test -bench') + go test -bench=. -benchmem $(if $(PKG),./$(PKG)/...,./...) + +.PHONY: local-image +local-image: ## Build local Docker image (dgraph/dgraph:local) + @echo building local docker image @GOOS=linux GOARCH=amd64 $(MAKE) dgraph @mkdir -p linux @mv ./dgraph/dgraph ./linux/dgraph @docker build -f contrib/Dockerfile -t dgraph/dgraph:local . @rm -r linux +.PHONY: image-local +image-local: local-image ## Alias for local-image + .PHONY: docker-image -docker-image: dgraph +docker-image: dgraph ## Build Docker image (dgraph/dgraph:$VERSION) @mkdir -p linux @cp ./dgraph/dgraph ./linux/dgraph docker build -f contrib/Dockerfile -t dgraph/dgraph:$(DGRAPH_VERSION) . @@ -93,14 +192,35 @@ linux-dependency: sudo apt-get -y install protobuf-compiler .PHONY: help -help: - @echo - @echo Build commands: - @echo " make [all] - Build all targets [EE]" - @echo " make dgraph - Build dgraph binary" - @echo " make install - Install all targets" - @echo " make uninstall - Uninstall known targets" - @echo " make version - Show current build info" - @echo " make help - This help" - @echo " make test - Make local image and run t.go" - @echo +help: ## Show available targets and variables + @echo "Usage: make [target] [VAR=value ...]" + @echo "" + @echo "Targets:" + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + sort | \ + awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' + @echo "" + @echo "Variables that can be passed to 'test':" + @echo " SUITE Select t/ runner suite (e.g., make test SUITE=systest)" + @echo " TAGS Go build tags - bypasses t/ runner (e.g., make test TAGS=integration2)" + @echo " PKG Limit to specific package (e.g., make test PKG=systest/export)" + @echo " TEST Run specific test function (e.g., make test TEST=TestGQLSchema)" + @echo " FUZZ Enable fuzz testing (e.g., make test FUZZ=1)" + @echo " FUZZTIME Fuzz duration per package (e.g., make test FUZZ=1 FUZZTIME=60s)" + @echo "" + @printf " Available SUITE values: " + @grep -o 'allowed := \[\]string{[^}]*}' t/t.go 2>/dev/null | \ + sed 's/allowed := \[\]string{"\([^}]*\)"}/\1/' | \ + tr -d '"' | tr ',' ' ' || echo "all, unit, core, systest, vector, ldbc, load" + @printf " Available TAGS values: " + @grep -roh "//go:build [a-z0-9]*" --include="*_test.go" . 2>/dev/null | \ + awk '{print $$2}' | \ + grep -E '^(integration|integration2|upgrade)$$' | \ + sort -u | tr '\n' ' ' && echo "" + @echo "" + @echo "Examples:" + @echo " make test TAGS=integration2 PKG=systest/vector # integration2 tests for vector" + @echo " make test TAGS=upgrade PKG=acl TEST=TestACL # specific upgrade test" + @echo " make test FUZZ=1 PKG=dql FUZZTIME=30s # fuzz dql package for 30s" + @echo " make test SUITE=systest PKG=systest/backup/filesystem # systest for backup pkg" + @echo " make test-benchmark PKG=posting # benchmark posting package" diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 00000000000..2651341d05e --- /dev/null +++ b/TESTING.md @@ -0,0 +1,1304 @@ +# Dgraph Testing Guide + +## Table of Contents + +- [Overview](#overview) +- [Test Types and Build Tags](#test-types-and-build-tags) +- [Module Structure](#module-structure) +- [Prerequisites & Setup](#prerequisites--setup) +- [Quick Start: Running Tests](#quick-start-running-tests) +- [Quick Decision Guide](#quick-decision-guide) +- [Unit Tests](#unit-tests) +- [Integration Tests via t/ Runner](#integration-tests-via-t-runner) +- [Running Integration Tests](#running-integration-tests) +- [Integration v2 Tests (integration2 build tag)](#integration-v2-tests-integration2-build-tag) +- [Upgrade Tests (upgrade build tag)](#upgrade-tests-upgrade-build-tag) +- [Writing Tests in Go (Dgraph Conventions)](#writing-tests-in-go-dgraph-conventions) +- [Fuzz Tests](#fuzz-tests) +- [Future Improvement Ideas](#future-improvement-ideas) + +--- + +## Overview + +Dgraph employs a ~~complex~~ sophisticated testing framework with extensive test coverage. The +codebase contains >200 test files with >2,000 test functions and benchmark functions across multiple +packages and modules. + +This guide helps engineers navigate testing in the Dgraph codebase. + +If you're making a change, you should be able to: + +- Choose the appropriate test type for your change (unit, integration, systest, upgrade, or fuzz) +- Identify where to add new tests in the repo +- Write Go tests that follow Dgraph's patterns +- Execute tests locally, single test, a whole package, or a named suite +- Debug CI failures by reproducing them on your own machine + +--- + +## Test Types and Build Tags + +The testing framework uses Go build tags to conditionally compile tests that are more costly to run. + +We distinguish the following types of tests: + +### 1. Unit Tests + +- **Purpose:** Test individual functions and components in isolation +- **Examples:** `dql/dql_test.go`, `types/value_test.go`, `schema/parse_test.go` +- **Build tags:** No build tags - Standard Go unit tests +- Unit tests run without any cluster and are usually fast. + +### 2. Integration Tests + +- **Purpose:** Test component interactions and full system workflows +- **Examples:** `acl/acl_test.go`, `worker/worker_test.go`, `query/query0_test.go` +- **Build tag:** `//go:build integration` + +### 3. Upgrade Tests + +- **Purpose:** Test database upgrade scenarios and migrations +- **Examples:** `acl/upgrade_test.go`, `worker/upgrade_test.go` +- **Build tag:** `//go:build upgrade` + +### 4. Benchmark Tests + +- **Function prefix:** `Benchmark` +- **Purpose:** Performance testing and optimization +- **Examples:** `query/benchmark_test.go`, `dql/bench_test.go` + +### 5. Cloud Tests (DEPRECATED) + +- **Purpose:** Test cloud-specific functionality +- **Examples:** `query/cloud_test.go`, `systest/cloud/cloud_test.go` +- **Build tag:** `//go:build cloud` + +Integration, Upgrade and Benchmark tests require a running Dgraph cluster (Docker) and come in two +forms: tests driven by the `t/` runner, and tests using the `dgraphtest` package, which provides +programmatic control over local Dgraph clusters. Most newer integration2 and upgrade tests rely on +`dgraphtest`. + +> **Note:** The `testutil` package is being phased out. For new tests, prefer `dgraphtest` (cluster +> management) and `dgraphapi` (client operations). The `testutil` package is maintained for backward +> compatibility with existing tests only. + +--- + +## Module Structure + +The main module is `github.com/hypermodeinc/dgraph` + +The codebase is organized into several key packages: + +### Core Packages + +| Package | Description | +| ------------ | ------------------------------------------ | +| `acl` | Access Control Lists and authentication | +| `algo` | Algorithms and data structures | +| `audit` | Audit logging functionality | +| `backup` | Backup and restore operations | +| `chunker` | Data chunking and parsing | +| `codec` | Encoding/decoding utilities | +| `conn` | Connection management | +| `dgraph` | Main Dgraph binary and commands | +| `dgraphapi` | Dgraph API client | +| `dgraphtest` | Testing utilities | +| `dql` | Dgraph Query Language parser and processor | +| `edgraph` | GraphQL endpoint | +| `filestore` | File storage abstraction | +| `graphql` | GraphQL implementation | +| `lex` | Lexical analysis | +| `posting` | Posting list management | +| `query` | Query processing engine | +| `raftwal` | Raft write-ahead log | +| `schema` | Schema management | +| `systest` | System integration tests | +| `testutil` | Testing utilities | +| `tok` | Tokenization and text processing | +| `types` | Data type definitions | +| `upgrade` | Database upgrade utilities | +| `worker` | Worker processes | +| `x` | Common utilities | + +--- + +## Prerequisites & Setup + +Before running tests, ensure you have the following installed and configured. + +> **TL;DR:** On a fresh checkout, just run `make install` followed by `make test`. The build system +> automatically handles OS detection, builds the correct binaries, and validates dependencies. + +### Automatic Dependency Checking + +The test framework includes scripts that check for required dependencies and can optionally +auto-install them: + +```bash +# Check all dependencies (run from t/ directory) +cd t && make check + +# Auto-install missing dependencies +AUTO_INSTALL=true make check +``` + +The check scripts validate: + +- Go version (1.21+) +- Docker and Docker Compose versions and memory allocation +- gotestsum installation +- ack installation +- Dgraph binary existence and correct architecture + +### Required Tools + +> **Note:** You don't need to install these manually. Running `AUTO_INSTALL=true make check` from +> the `t/` directory (or `AUTO_INSTALL=true make test` from the repo root) automatically installs +> missing dependencies. The commands below are listed for reference. + +#### 1. Go (1.21+) + +```bash +go version # Verify Go is installed +``` + +#### 2. Docker (20.10 or higher) & Docker Compose (v2) + +```bash +docker --version +docker compose version + +# Allocate sufficient memory: 4GB minimum, 8GB recommended +# Docker Desktop → Settings → Resources → Memory +``` + +#### 3. GOPATH Configuration + +```bash +# Set GOPATH (if not already set) +export GOPATH=$(go env GOPATH) +echo $GOPATH # Should output something like /Users/you/go + +# Add to your shell profile (~/.zshrc, ~/.bashrc) +export GOPATH=$(go env GOPATH) +export PATH=$PATH:$GOPATH/bin +``` + +#### 4. gotestsum (Required for t/ runner) + +```bash +go install gotest.tools/gotestsum@latest + +# Verify installation +gotestsum --version +``` + +#### 5. ack (required for test framework t/) + +```bash +brew install ack +``` + +#### 6. Dgraph Binary (Required for integration/upgrade tests) + +```bash +# Build and install Dgraph binary to $GOPATH/bin +make install + +# Verify installation +which dgraph # Should show $GOPATH/bin/dgraph +dgraph version +``` + +> **Note:** The `t/` runner's Docker Compose files mount the dgraph binary into containers at +> startup. On macOS, binaries are read from `$GOPATH/linux_/dgraph`; on Linux, from +> `$GOPATH/bin/dgraph`. Simply run `make install` after code changes — no Docker image rebuild +> needed. + +### Quick Setup + +The build system now handles most setup automatically. On both Linux and macOS: + +```bash +# Install dependencies (optional - auto-installs if missing) +cd t && AUTO_INSTALL=true make check && cd .. + +# Build dgraph binary (automatically handles Linux binary on macOS) +make install + +# Run tests (builds Docker image and runs test suite) +make test +``` + +That's it! The `make install` command: + +- On **Linux**: Installs dgraph to `$GOPATH/bin/dgraph` +- On **macOS**: Installs native binary to `$GOPATH/bin/dgraph` AND Linux binary to + `$GOPATH/linux_/dgraph` + +The Docker Compose files automatically use the correct binary path via the `LINUX_GOBIN` environment +variable. + +### macOS Notes + +The build system now automatically handles cross-compilation for macOS users: + +- `make install` builds both native macOS and Linux binaries automatically +- Linux binaries are stored in `$GOPATH/linux_/dgraph` +- Docker Compose files use `${LINUX_GOBIN:-$GOPATH/bin}` to find the correct binary +- No manual binary swapping required! + +After code changes, simply run `make install` again — it handles everything. + +### Special Case: Bulk/Live Loader Tests on macOS + +**Background:** Bulk and live loader tests (`systest/bulk_live/`) execute `dgraph bulk` and +`dgraph live` commands locally on your machine (not inside Docker). + +**Good news:** Since `make install` now builds both binaries on macOS, you have: + +1. Native macOS binary at `$GOPATH/bin/dgraph` (used for local commands) +2. Linux binary at `$GOPATH/linux_/dgraph` (used by Docker containers) + +### Verify Your Setup + +#### Step 1: Verify unit tests work + +Use `go test` to run one easy test on types package: + +```bash +go test -v ./types/... -run TestConvert +``` + +**Expected output:** + +```text +=== RUN TestConvertToDefault +--- PASS: TestConvertToDefault (0.00s) +... +=== RUN TestConvertToGeoJson_PolyError2 +--- PASS: TestConvertToGeoJson_PolyError2 (0.00s) +PASS +ok github.com/dgraph-io/dgraph/v25/types (cached) +? github.com/dgraph-io/dgraph/v25/types/facets [no test files] +``` + +#### Step 2: Verify integration test setup + +> **Note:** Start Docker Desktop before running integration or upgrade tests + +```bash +cd t && go build . && ./t --test=TestGQLSchema +``` + +If both pass, you're ready to run all test types! + +--- + +## Quick Start: Running Tests + +### Using Make Targets + +The simplest way to run tests: + +```bash +# Run all tests (default) +make test + +# Common shortcuts (run 'make help' for full list) +make test-all # All test suites via t/ runner (i.e. 'make test SUITE=all') +make test-unit # Unit tests, no Docker (i.e. 'make test SUITE=unit') +make test-core # Core tests (i.e. 'make test SUITE=core') +make test-systest # System integration tests (i.e. 'make test SUITE=systest') +make test-vector # Vector search tests (i.e. 'make test SUITE=vector') +make test-ldbc # LDBC benchmark tests (i.e. 'make test SUITE=ldbc') +make test-load # Heavy load tests (i.e. 'make test SUITE=load') +make test-integration # Integration tests (i.e. 'make test TAGS=integration') +make test-integration2 # Integration2 tests via dgraphtest (i.e. 'make test TAGS=integration2') +make test-upgrade # Upgrade tests (i.e. 'make test TAGS=upgrade') +make test-fuzz # Fuzz tests, auto-discovers packages (i.e. 'make test FUZZ=1') +make test-benchmark # Go benchmarks (i.e. 'go test -bench') +``` + +Run `make help` to see all available targets, variables, and dynamically discovered SUITE/TAGS +values. + +### Using Variables + +For more control, pass variables to `make test`: + +| Variable | Purpose | Example | +| ---------- | ---------------------------------- | ------------------------------- | +| `SUITE` | Select t/ runner suite | `make test SUITE=systest` | +| `TAGS` | Go build tags - bypasses t/ runner | `make test TAGS=integration2` | +| `PKG` | Limit to specific package | `make test PKG=systest/export` | +| `TEST` | Run specific test function | `make test TEST=TestGQLSchema` | +| `FUZZ` | Enable fuzz testing | `make test FUZZ=1` | +| `FUZZTIME` | Fuzz duration per package | `make test FUZZ=1 FUZZTIME=60s` | + +**Precedence:** `TAGS` > `FUZZ` > `SUITE` (first match wins) + +### Examples + +```bash +# Run integration2 tests for vector package +make test TAGS=integration2 PKG=systest/vector + +# Run upgrade tests for ACL with specific test +make test TAGS=upgrade PKG=acl TEST=TestACL + +# Run fuzz tests with custom duration +make test FUZZ=1 PKG=dql FUZZTIME=30s + +# Run systest for backup package +make test SUITE=systest PKG=systest/backup/filesystem + +# Benchmark a specific package +make test-benchmark PKG=posting +``` + +--- + +## Quick Decision Guide + +Use this section to quickly determine what test to write and where to place it. + +### Testing Philosophy + +Cover as many scenarios as possible. A good PR includes tests for: + +- The happy path (expected behaviour) +- Edge cases (empty inputs, boundary values, special characters) +- Error conditions (invalid inputs, failure modes) + +Use a layered testing approach. Aim for broad coverage with unit tests to validate individual +functions and quickly identify failures, and complement them with integration and end-to-end tests +for cluster-dependent behavior and real-world scenarios. Each test type is important and they should +be mutually reinforcing. + +### Unit Tests: Test everything you can without a running Dgraph cluster + +Unit tests run without a Dgraph cluster. They test pure logic in isolation. + +- Place it in the same package as the code you changed +- File name: `*_test.go` next to the source file +- No build tag needed + +**Example:** Changing `worker/export.go` → add test in `worker/export_test.go` + +### Integration Tests: cover scenarios requiring a running Dgraph cluster + +Testing individual functions and components in isolation is usually not enough. Integration Tests +test component interactions and full system workflows. They require a running Dgraph cluster. + +### What are Go build tags? + +Go build tags are special comments at the top of a file (for example, `//go:build integration`) that +instruct the Go toolchain when to compile that file. When you run tests with +`go test -tags=integration`, only test files without a build tag (default) or with a matching tag +are compiled and executed. + +We use build tags to exclude expensive or environment-dependent tests (like `integration`, +`integration2`, and `upgrade`) from the default `go test ./...` run, while allowing you to opt in to +them when needed. + +| Build Tag | Purpose | +| -------------------- | ----------------------------------------------------------------- | +| `integration` | Standard integration tests requiring a Docker cluster | +| `integration2` | Integration tests using Docker Go client via `dgraphtest` package | +| `upgrade` | Tests for upgrade scenarios between dgraph versions | +| `cloud` (deprecated) | Tests running against cloud environment | + +### Test Placement Guide + +| If you're testing... | Test type | Build tag | Where to place | +| ----------------------------------------------- | ------------ | -------------- | ---------------------------------------------- | +| Query or mutation logic | Integration | `integration` | Existing package or `systest/` | +| Backup / Restore | Integration | `integration` | `systest/backup/` or `systest/online-restore/` | +| Export | Integration | `integration` | `systest/export/` | +| Live loader / Bulk loader | Integration | `integration` | `systest/bulk_live/` or `systest/loader/` | +| Multi-tenancy / Namespaces | Integration | `integration` | `systest/multi-tenancy/` | +| Vector / Embeddings | Integration | `integration` | `systest/vector/` | +| GraphQL schema or endpoints | Integration | `integration` | `graphql/e2e/` | +| ACL / Auth | Integration | `integration` | `acl/` or `systest/acl/` | +| Upgrade from older version | Upgrade | `upgrade` | Same package with `//go:build upgrade` | +| Fine-grained cluster control (start/stop nodes) | Integration2 | `integration2` | `systest/integration2/` or relevant package | + +### Quick Examples + +- **I fixed a bug in query parsing (no cluster needed to fully validate)** → Unit test in + `query/*_test.go`, no build tag + +- **I fixed a bug in export that affects vector data** → Integration test in `systest/vector/`, use + `dgraphtest.LocalCluster`, tag: `//go:build integration` + +- **I changed backup behaviour** → Integration test in `systest/backup/`, tag: + `//go:build integration` + +- **I need to test behaviour after upgrading from v23 to main** → Upgrade test in relevant package, + tag: `//go:build upgrade` + +- **I changed GraphQL admin endpoint** → Integration test in `graphql/e2e/`, tag: + `//go:build integration` + +### Rules of Thumb + +1. **Maximize unit test coverage.** If you can fully test it without a cluster - unit tests only. If + it can't be tested at all without a cluster, integration tests only. Otherwise add a mix of both + unit and integration tests – unit tests for what parts can be tested in isolation and integration + tests for the remainder. + +2. **Cover multiple scenarios.** Don't just test the happy path—include edge cases and error + conditions. + +3. **Use table-driven tests.** One test function with multiple cases beats many separate functions. + +4. **No flaky tests.** Avoid `time.Sleep()`; use polling, retries, or explicit waits with timeouts. + +5. **Follow existing patterns.** Look at nearby `*_test.go` files and match their style. + +--- + +## Unit Tests + +### Running Go Tests + +#### Basic Command Structure + +```bash +go test [flags] [package] [test-filter] +``` + +#### Common Flags + +- `-v` (verbose): Shows detailed output for each test +- `-run `: Run only tests matching the pattern (regex) + +#### Package Paths + +- `./types/`: Single package +- `./types/...`: Package and all subpackages recursively + +#### Go Test Examples + +```bash +# Run all tests in types package +go test ./types/ + +# Run all tests in types and subpackages +go test ./types/... + +# Run specific test with verbose output +go test -v ./types/... -run TestConvert +``` + +### Identifying a unit test + +- No `//go:build` tag at the top of the file = unit test +- Files with `//go:build integration` are NOT unit tests + +### Where to place unit tests + +Place `*_test.go` next to the code being tested: + +| Code in | Test in | +| --------------------- | -------------------------- | +| `types/conversion.go` | `types/conversion_test.go` | +| `dql/parser.go` | `dql/parser_test.go` | +| `schema/parse.go` | `schema/parse_test.go` | + +### When to write a unit test + +- Parsing logic +- Data conversions +- Utility functions +- Algorithms +- Any code that doesn't need cluster access + +--- + +## Integration Tests via t/ Runner + +The `t/` runner orchestrates Docker-based integration tests. It spins up Dgraph clusters using +Docker Compose and runs tests tagged with `integration`. + +### How it works + +1. Uses Dgraph binary from `$GOPATH/bin/dgraph` +2. Spins up cluster via `docker-compose.yml` (package-specific or default) +3. Runs tests with `--tags=integration` +4. Tears down cluster after completion + +### Test Suites + +A suite is a named group of test packages that can be run together with the `--suite` flag. + +| Suite | Purpose | Packages/Tests Included | +| --------- | ------------------------------------- | --------------------------------------------------------------------------------------------- | +| `unit` | Default suite for regular development | All packages except ldbc and load (includes query, mutation, schema, GraphQL, ACL, worker) | +| `core` | Core Dgraph functionality | Query, mutation, schema, GraphQL e2e, ACL, TLS, worker (excludes systest, ldbc, vector, load) | +| `systest` | Real workflows and system-level tests | Backup/restore, export, multi-tenancy, online-restore, audit, CDC, group-delete | +| `vector` | Vector search functionality | Vector index, similarity search, HNSW, vector backup/restore (`systest/vector/`) | +| `ldbc` | Benchmark queries | LDBC benchmark suite (`systest/ldbc/`) | +| `load` | Heavy data loading scenarios | 21million, 1million, bulk_live, bgindex, bulkloader | +| `all` | Everything | Runs all test suites | + +### Docker Compose Discovery + +The runner looks for `docker-compose.yml`: + +1. First in the test package directory (e.g., `systest/export/docker-compose.yml`) +2. Falls back to default: `dgraph/docker-compose.yml` + +Tests with custom compose files run in isolated clusters. + +### Common Commands + +```bash +# Build the runner first +cd t && go build . + +# Run a suite +./t --suite=core + +# Run specific package +./t --pkg=systest/export + +# Run single test +./t --test=TestExportAndLoadJson + +# Keep cluster after test (for debugging) +./t --pkg=systest/export --keep + +# Cleanup all test containers +./t -r +``` + +### Key Flags + +| Flag | Description | +| ------------- | ------------------------------------------------------------------ | +| `--suite=X` | Select test suite(s): all, ldbc, load, unit, systest, vector, core | +| `--pkg=X` | Run specific package | +| `--test=X` | Run specific test function | +| `-j=N` | Concurrency (default: 1) | +| `--keep` | Keep cluster running after tests | +| `-r` | Remove all test containers | +| `--skip-slow` | Skip slow packages | + +--- + +## Running Integration Tests + +### Method 1: Using t/ Runner + +The `t/` runner manages cluster lifecycle automatically. + +```bash +# Build runner +cd t && go build . + +# Run all tests in a package +./t --pkg=systest/export + +# Run single test +./t --test=TestExportAndLoadJson + +# Keep cluster running after tests (for debugging) +./t --pkg=systest/export --keep +``` + +### Method 2: Manual Cluster + go test + +For fine-grained control, manually start a cluster and run tests against it. + +#### Step 1: Start cluster with Docker Compose + +```bash +# Start default cluster with a custom prefix +docker compose -f dgraph/docker-compose.yml -p mytest up -d + +# Or start package-specific cluster +docker compose -f systest/export/docker-compose.yml -p mytest up -d +``` + +#### Step 2: Set environment variable and run tests + +```bash +# Set the prefix (tells testutil which cluster to use) +export TEST_DOCKER_PREFIX=mytest + +# Run all tests in package +go test -v --tags=integration ./systest/export/... + +# Run single test +go test -v --tags=integration --run '^TestExportAndLoadJson$' ./systest/export/ + +# Run multiple specific tests +go test -v --tags=integration --run 'TestExport.*' ./systest/export/ +``` + +#### Step 3: Cleanup + +```bash +docker compose -f dgraph/docker-compose.yml -p mytest down -v +``` + +### Method 3: Use Existing Cluster with t/ Runner + +```bash +# Start cluster manually first +docker compose -f dgraph/docker-compose.yml -p myprefix up -d + +# Run tests against it (no cluster restart) +cd t && ./t --prefix=myprefix --pkg=systest/export + +# Cluster stays running after tests +``` + +### Running Multiple Tests + +Using `go test` regex: + +```bash +export TEST_DOCKER_PREFIX=mytest + +# All tests matching pattern +go test -v --tags=integration --run 'TestExport' ./systest/export/ + +# Multiple test names +go test -v --tags=integration --run 'TestExportAndLoad|TestExportSchema' ./systest/export/ +``` + +Using `t/` runner: + +```bash +# Run all tests in multiple packages +./t --pkg=systest/export,systest/backup/filesystem + +# Run entire suite +./t --suite=systest +``` + +### Key Environment Variables + +| Variable | Purpose | Set by | +| --------------------- | ---------------------------------- | ------------------- | +| `TEST_DOCKER_PREFIX` | Docker Compose prefix for cluster | t/ runner or manual | +| `TEST_DATA_DIRECTORY` | Path to test data files | t/ runner | +| `GOPATH` | Required for finding dgraph binary | User | + +--- + +## Integration v2 Tests (integration2 build tag) + +Uses `dgraphtest` package for programmatic cluster control via Docker Go client. + +### Why It Exists + +- **Problem:** t/ runner can't handle upgrade tests, individual node control, or version switching +- **Solution:** Full programmatic control over cluster lifecycle through Go API +- **Use when:** Testing upgrades, node failures, or needing precise cluster state control + +> **Important:** `dgraphtest` and `dgraphapi` are the future direction for Dgraph testing. New tests +> should use these packages instead of `testutil`. The `testutil` package is being retired and +> maintained only for backward compatibility with existing tests. + +### Key Differences from t/ Runner + +| Feature | t/ runner | integration2 | +| ----------------------- | -------------- | ------------------------------ | +| Cluster management | docker-compose | Docker Go client | +| Version switching | No | Yes | +| Individual node control | No | Yes (Start/Stop/Kill per node) | +| Upgrade testing | No | Yes | +| Build tag | `integration` | `integration2` | + +### How to Run + +```bash +# Build your local binary first +make install + +# Run tests +go test -v --tags=integration2 ./systest/integration2/ +go test -v --tags=integration2 --run '^TestName$' ./pkg/ +``` + +### Version & Binary Management + +**Automatic version handling:** + +- Clones Dgraph repo to `/tmp/dgraph-repo-*` on first run +- Checks out requested version (tag/commit) +- Builds binary with `make dgraph` (GOOS=linux) +- Caches in `dgraphtest/binaries/dgraph_` + +**Version formats:** + +- `"local"` - uses `$GOPATH/bin/dgraph` (default) +- `"v23.0.1"` - git tag +- `"4fc9cfd"` - commit hash + +First run is slow (builds binaries), subsequent runs reuse cache. + +### The dgraphapi Package + +`dgraphapi` provides high-level client wrappers for interacting with Dgraph in tests. + +**Two client types:** + +#### GrpcClient - For DQL operations + +- Wraps `dgo.Dgraph` +- Handles queries, mutations, schema operations +- Login/authentication +- Namespace operations +- UID assignment + +#### HTTPClient - For admin/HTTP operations + +- Backup operations (full and incremental) +- Restore operations +- Namespace management (add/delete) +- Snapshot management +- Health checks +- State endpoint queries +- GraphQL operations +- Export operations + +Both clients support authentication and multi-tenancy (namespace-aware operations). + +### Gotchas & Important Notes + +#### 1. Prerequisites + +- `$GOPATH/bin/dgraph` must exist for "local" version +- `GOPATH` environment variable must be set +- Docker with sufficient resources (4GB+ memory) + +#### 2. Always cleanup + +- Defer cleanup after cluster creation +- Defer client cleanup after getting clients +- Cleanup removes containers, networks, volumes + +#### 3. Authentication required + +- Both clients need login for ACL-enabled clusters +- Default credentials: `groot` / `password` +- Must specify namespace (typically root namespace = 0) + +#### 4. Performance expectations + +- First run: 3-5 minutes (clone + build) +- Subsequent runs: normal test speed (reuses binaries) +- Binary cache shared across parallel tests safely + +#### 5. When NOT to use integration2 + +- Simple query/mutation tests → use t/ runner (faster) +- Don't need version switching → use t/ runner +- Don't need individual node control → use t/ runner + +### Bonus: Using dgraphapi with Your Local Cluster + +The `dgraphapi` package can work with any running Dgraph cluster. If no Docker prefix is detected +(no `TEST_DOCKER_PREFIX` env var), it falls back to localhost ports. + +**Default fallback ports:** + +- Alpha gRPC: `localhost:9080` +- Alpha HTTP: `localhost:8080` +- Zero gRPC: `localhost:5080` +- Zero HTTP: `localhost:6080` + +**Use case:** Write quick Go scripts to interact with your local development cluster instead of +using Postman for repetitive tasks. + +**Benefits:** + +- Automate repetitive admin operations +- Test admin workflows quickly +- Reuse test helpers for local development +- Type-safe operations instead of manual JSON crafting + +This is especially useful for testing backup/restore, namespace operations, or complex mutation +sequences during development. + +**Example test:** + +```go +func TestLocalCluster(t *testing.T) { + c := dgraphtest.NewComposeCluster() + + gc, cleanup, err := c.Client() + require.NoError(t, err) + defer cleanup() + + require.NoError(t, gc.SetupSchema(testSchema)) + + numVectors := 9 + rdfs, _ := dgraphapi.GenerateRandomVectors(0, numVectors, 100, pred) + mu := &api.Mutation{SetNquads: []byte(rdfs), CommitNow: true} + _, err = gc.Mutate(mu) + require.NoError(t, err) +} +``` + +--- + +## Upgrade Tests (upgrade build tag) + +Tests that verify Dgraph behaviour when upgrading from one version to another. + +### Why Upgrade Tests Matter + +- Ensure backward compatibility across versions +- Catch breaking changes in data format, schema, or behaviour +- Validate that existing data survives upgrades +- Test real-world upgrade workflows customers use + +### Build Tag + +```go +//go:build upgrade + +package main + +func TestUpgradeFromV23(t *testing.T) { + // Start with old version + conf := dgraphtest.NewClusterConfig().WithVersion("v23.0.1") + // ... test upgrade to "local" ... +} +``` + +### Upgrade Strategies + +| Strategy | How it works | Use case | +| --------------- | ------------------------------------------ | ---------------------------------------- | +| `BackupRestore` | Take backup on old version, restore on new | Most common customer upgrade path | +| `InPlace` | Stop cluster, swap binary, restart | Fast upgrade, tests binary compatibility | +| `ExportImport` | Export from old, import to new | Migration across major versions | + +Specified when calling `c.Upgrade()`: + +```go +c.Upgrade("local", dgraphtest.BackupRestore) +c.Upgrade("local", dgraphtest.InPlace) +c.Upgrade("local", dgraphtest.ExportImport) +``` + +### Version Combinations + +Controlled by `DGRAPH_UPGRADE_MAIN_ONLY` environment variable: + +**`DGRAPH_UPGRADE_MAIN_ONLY=true` (default):** + +- Tests only latest stable → HEAD +- Example: v24.0.0 → local +- Runs in PR CI (fast) + +**`DGRAPH_UPGRADE_MAIN_ONLY=false`:** + +- Tests many historical versions → HEAD +- Includes v21, v22, v23, v24 releases +- Includes specific cloud commits +- Runs in scheduled CI (comprehensive but slow) + +### Running Upgrade Tests + +**Run all upgrade tests:** + +```bash +# Build your local binary first +make install + +# Run with main combos only (fast) +go test -v --tags=upgrade ./... + +# Run with all version combos (slow, 30min+) +DGRAPH_UPGRADE_MAIN_ONLY=false go test -v --tags=upgrade ./... +``` + +**Run specific package:** + +```bash +go test -v --tags=upgrade ./systest/mutations-and-queries/ +go test -v --tags=upgrade ./acl/ +go test -v --tags=upgrade ./worker/ +``` + +**Run single test:** + +```bash +go test -v --tags=upgrade -run '^TestUpgradeName$' ./pkg/ +``` + +### Where Upgrade Tests Live + +| Package | Tests | +| -------------------------------- | --------------------------------- | +| `systest/mutations-and-queries/` | Data preservation across upgrades | +| `systest/multi-tenancy/` | Namespace/ACL upgrade behaviour | +| `systest/plugin/` | Custom plugin compatibility | +| `acl/` | ACL schema and permissions | +| `worker/` | Worker-level upgrade logic | +| `query/` | Query behaviour consistency | + +--- + +## Writing Tests in Go (Dgraph Conventions) + +Dgraph follows standard Go testing patterns with specific conventions. + +### Test Naming + +**Function names:** + +- Start with `Test`: `TestParseSchema`, `TestQueryExecution` +- Use camelCase: `TestBackupAndRestore`, not `Test_Backup_And_Restore` +- Be descriptive: `TestVectorIndexRebuilding` not `TestVector` + +**File names:** + +- End with `_test.go`: `parser_test.go`, `backup_test.go` +- Match the source file: `schema.go` → `schema_test.go` + +### Table-Driven Tests (Preferred) + +Used extensively in Dgraph for testing multiple scenarios: + +```go +func TestConversion(t *testing.T) { + tests := []struct { + name string + input Val + output Val + wantErr bool + }{ + {name: "string to int", input: Val{...}, output: Val{...}}, + {name: "invalid type", input: Val{...}, wantErr: true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := Convert(tc.input, tc.output.Tid) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.output, got) + }) + } +} +``` + +**Benefits:** + +- Test multiple cases in one function +- Easy to add new test cases +- Clear failure messages with `t.Run` + +### Assertions: require vs assert + +Dgraph uses the `testify` library: + +**`require.*` (fail immediately):** + +```go +require.NoError(t, err) // Stops test if err != nil +require.Equal(t, expected, actual) +require.True(t, condition) +``` + +When to use: Setup, critical checks, integration tests + +**`assert.*` (continue on failure):** + +```go +assert.NoError(t, err) // Logs error but continues +assert.Equal(t, expected, actual) +``` + +When to use: Rarely in Dgraph; prefer `require` for clarity + +**Convention:** Use `require` by default. + +### Subtests with t.Run + +Creates isolated subtests with individual names: + +```go +func TestCluster(t *testing.T) { + t.Run("start nodes", func(t *testing.T) { + // subtest 1 + }) + + t.Run("health check", func(t *testing.T) { + // subtest 2 + }) +} +``` + +**Benefits:** + +- Run specific subtest: `go test --run TestCluster/health` +- Better failure isolation +- Clearer test output + +### Cleanup with t.Cleanup + +Always defer cleanup operations: + +```go +func TestWithCluster(t *testing.T) { + c, err := dgraphtest.NewLocalCluster(conf) + require.NoError(t, err) + defer func() { c.Cleanup(t.Failed()) }() // Cleanup even if test fails + + gc, cleanup, err := c.Client() + require.NoError(t, err) + defer cleanup() // Close client connections +} +``` + +**Why:** Ensures resources are freed even on test failure. + +### Helper Functions with t.Helper() + +Mark helper functions so failures point to actual test line: + +```go +func setupTestData(t *testing.T, gc *GrpcClient) { + t.Helper() // Failures show caller line, not this line + + err := gc.SetupSchema(`name: string .`) + require.NoError(t, err) +} + +func TestSomething(t *testing.T) { + setupTestData(t, gc) // If this fails, error points here + // ... +} +``` + +### Anti-Patterns to Avoid + +#### ❌ Don't use time.Sleep for synchronization + +```go +// BAD +time.Sleep(5 * time.Second) // Flaky! + +// GOOD +require.NoError(t, c.HealthCheck(false)) // Wait for actual condition +``` + +#### ❌ Don't share mutable state between tests + +```go +// BAD +var sharedClient *Client // Tests interfere with each other + +// GOOD +func TestX(t *testing.T) { + client := newClient() // Each test gets its own +} +``` + +#### ❌ Don't depend on test execution order + +```go +// BAD - Test2 depends on Test1 running first +func TestInsertData(t *testing.T) { /* insert */ } +func TestQueryData(t *testing.T) { /* assumes data exists */ } + +// GOOD - Each test is independent +func TestQuery(t *testing.T) { + setupData(t) // Set up what you need + // ... test query +} +``` + +#### ❌ Don't ignore errors in tests + +```go +// BAD +client.Mutate(mutation) // Ignoring error + +// GOOD +_, err := client.Mutate(mutation) +require.NoError(t, err) +``` + +### Parallelization + +Use with caution: + +```go +func TestIndependent(t *testing.T) { + t.Parallel() // Can run in parallel with other tests + // Only if test doesn't share resources +} +``` + +**Don't use for:** + +- Integration tests sharing clusters +- Tests modifying global state +- Tests using same ports/resources + +### Test Suites with testify/suite + +Dgraph uses `testify/suite` for tests needing shared setup/teardown across multiple test methods. + +**When to use:** + +- Multiple related test methods sharing the same cluster +- Need setup/teardown hooks (`SetupTest`, `TearDownTest`) +- Upgrade tests that run the same tests across version combinations +- Sharing test logic between integration and upgrade tests + +**Benefits:** + +- Reduces boilerplate for shared setup +- Each test method is independent (new setup/teardown) +- Same test methods run for both integration and upgrade tests +- Excellent for upgrade tests (run same tests across version combos) + +**Key pattern: Shared test logic across build tags** + +Dgraph uses suites to run identical test methods for both integration and upgrade tests: + +**Integration suite (`//go:build integration`):** + +- Creates cluster once +- Runs test methods +- Tests current version behaviour + +**Upgrade suite (`//go:build upgrade`):** + +- Creates cluster with old version +- Runs test methods (validates data works on old version) +- Calls `Upgrade()` method +- Runs same test methods again (validates data still works after upgrade) + +**Available hooks:** + +- `SetupSuite()` - once before all tests +- `SetupTest()` - before each test method +- `SetupSubTest()` - before each subtest +- `TearDownTest()` - after each test method +- `TearDownSuite()` - once after all tests + +**How to run:** + +```bash +# Run entire test suite (all test methods) +go test -v --tags=integration ./systest/plugin/ + +# Run specific test method from suite +go test -v --tags=integration --run 'TestPluginTestSuite/TestPasswordReturn' ./systest/plugin/ + +# Run specific subtest within a test method +go test -v --tags=integration --run 'TestPluginTestSuite/TestPasswordReturn/subtest' ./systest/plugin/ + +# Run same tests in upgrade mode +go test -v --tags=upgrade --run 'TestPluginTestSuite/TestPasswordReturn' ./systest/plugin/ +``` + +**When NOT to use:** + +- Simple one-off tests → use regular `func TestX(t *testing.T)` +- No shared setup needed → suites add unnecessary complexity +- Unit tests → keep simple + +**Examples in Dgraph codebase:** + +- `acl/integration_test.go` + `acl/acl_integration_test.go` - ACL suite +- `systest/plugin/` - Integration + Upgrade suites sharing test methods +- `systest/mutations-and-queries/` - Integration + Upgrade suites + +--- + +## Fuzz Tests + +Fuzzing tests parser and validation logic with random inputs to find edge cases. + +### What is Fuzzing? + +Go's native fuzzing generates random inputs to find crashes, panics, or unexpected behaviour. + +### Where Fuzz Tests Live + +- `dql/parser_fuzz_test.go` - DQL query parser fuzzing + +### Running Fuzz Tests + +```bash +# Run fuzz test for 5 minutes +go test -v ./dql -fuzz=Fuzz -fuzztime=5m + +# Run with custom timeout +go test -v ./dql -fuzz=Fuzz -fuzztime=300s -fuzzminimizetime=120s +``` + +### CI Workflow + +- `ci-dgraph-fuzz.yml` (runs on PRs) +- Runs: `go test -v ./dql -fuzz="Fuzz" -fuzztime="300s"` +- Timeout: 10 minutes +- Catches parser crashes early + +### When to Write Fuzz Tests + +- Parsers (DQL, GraphQL, RDF) +- Input validation +- Decoders/deserializers +- Any code accepting untrusted input + +--- + +## Future Improvement Ideas + +### ✅ Completed Improvements + +The following items from the original wishlist have been implemented: + +- **✅ OS detection and automatic binary handling:** The Makefile now detects the host OS at runtime + and automatically builds the correct binaries. On macOS, `make install` builds both native and + Linux binaries without manual intervention. + +- **✅ Automatic binary path management:** The `LINUX_GOBIN` environment variable is automatically + set based on OS. Docker Compose files use `${LINUX_GOBIN:-$GOPATH/bin}` to mount the correct + binary. + +- **✅ No manual setup scripts required:** The `make test` target now depends on `dgraph-installed` + which automatically builds binaries if missing. Dependency checking scripts in `t/scripts/` can + auto-install missing tools with `AUTO_INSTALL=true`. + +- **✅ Prerequisites handled automatically:** Running `make test` validates dependencies and builds + required binaries before running tests. + +- **✅ Unified test interface:** A single `make test` entry point that accepts arguments to run any + test type (unit, integration, integration2, upgrade, fuzz) with environment variables for control. + +- **✅ Example commands that "just work":** The following now work as expected: + + ```bash + make test SUITE=systest + make test FUZZ=1 PKG=dql + make test TAGS=upgrade PKG=acl + make test TAGS=integration PKG=systest/plugin + ``` + +### Remaining Ideas + +The following improvements could still enhance the developer experience: + +- **Extend t/ runner:** Have the `t/` runner also handle unit and integration2 tests, providing a + consistent interface for all test types. diff --git a/dgraph/Makefile b/dgraph/Makefile index 2ae3993a0d0..6608079782e 100644 --- a/dgraph/Makefile +++ b/dgraph/Makefile @@ -86,7 +86,7 @@ install: jemalloc fi @go mod tidy @go install $(BUILD_FLAGS) - @echo "Installed $(BIN) to $(INSTALL_TARGET)" + @echo "Installed $(BIN) ($(GOOS)/$(GOARCH)) to $(INSTALL_TARGET)" @if [ "$(HAS_SHA256SUM)" ] ; then \ echo "New SHA256:" `sha256sum $(INSTALL_TARGET) 2>/dev/null | cut -c-64` ; \ fi diff --git a/dgraph/docker-compose.yml b/dgraph/docker-compose.yml index b8f1411e6db..8bf64de8798 100644 --- a/dgraph/docker-compose.yml +++ b/dgraph/docker-compose.yml @@ -11,7 +11,7 @@ services: service: zero volumes: - type: bind - source: ${GOPATH:?GOPATH environment variable is required but not set}/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -32,7 +32,7 @@ services: service: zero volumes: - type: bind - source: ${GOPATH:?GOPATH environment variable is required but not set}/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -52,7 +52,7 @@ services: service: zero volumes: - type: bind - source: ${GOPATH:?GOPATH environment variable is required but not set}/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -64,7 +64,7 @@ services: working_dir: /data/alpha1 volumes: - type: bind - source: ${GOPATH:?GOPATH environment variable is required but not set}/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -94,7 +94,7 @@ services: - alpha1 volumes: - type: bind - source: ${GOPATH:?GOPATH environment variable is required but not set}/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -124,7 +124,7 @@ services: - alpha2 volumes: - type: bind - source: ${GOPATH:?GOPATH environment variable is required but not set}/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -154,7 +154,7 @@ services: - alpha3 volumes: - type: bind - source: ${GOPATH:?GOPATH environment variable is required but not set}/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -184,7 +184,7 @@ services: - alpha4 volumes: - type: bind - source: ${GOPATH:?GOPATH environment variable is required but not set}/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -214,7 +214,7 @@ services: - alpha5 volumes: - type: bind - source: ${GOPATH:?GOPATH environment variable is required but not set}/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/1million/docker-compose.yml b/systest/1million/docker-compose.yml index 12a264f958e..80a8dd4cbcc 100644 --- a/systest/1million/docker-compose.yml +++ b/systest/1million/docker-compose.yml @@ -10,7 +10,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: volume diff --git a/systest/21million/bulk/docker-compose.yml b/systest/21million/bulk/docker-compose.yml index 12a264f958e..80a8dd4cbcc 100644 --- a/systest/21million/bulk/docker-compose.yml +++ b/systest/21million/bulk/docker-compose.yml @@ -10,7 +10,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: volume diff --git a/systest/21million/live/docker-compose.yml b/systest/21million/live/docker-compose.yml index 122585e267a..361ba6db710 100644 --- a/systest/21million/live/docker-compose.yml +++ b/systest/21million/live/docker-compose.yml @@ -12,7 +12,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -28,7 +28,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/systest/acl/restore/docker-compose.yml b/systest/acl/restore/docker-compose.yml index 803e5a000f9..37f682d4fb8 100644 --- a/systest/acl/restore/docker-compose.yml +++ b/systest/acl/restore/docker-compose.yml @@ -10,7 +10,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -38,7 +38,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/systest/audit/docker-compose.yml b/systest/audit/docker-compose.yml index 189ed9ae921..3b3a05471ef 100644 --- a/systest/audit/docker-compose.yml +++ b/systest/audit/docker-compose.yml @@ -8,7 +8,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -28,7 +28,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/audit_encrypted/docker-compose.yml b/systest/audit_encrypted/docker-compose.yml index 9d0cca1cb9b..d063d81672c 100644 --- a/systest/audit_encrypted/docker-compose.yml +++ b/systest/audit_encrypted/docker-compose.yml @@ -8,7 +8,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -32,7 +32,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/backup/advanced-scenarios/127-Namespace/docker-compose.yml b/systest/backup/advanced-scenarios/127-Namespace/docker-compose.yml index 5fdb0deb180..e0644d083a0 100755 --- a/systest/backup/advanced-scenarios/127-Namespace/docker-compose.yml +++ b/systest/backup/advanced-scenarios/127-Namespace/docker-compose.yml @@ -14,7 +14,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -37,7 +37,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - data-volume:/data/backups/ @@ -57,7 +57,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -79,7 +79,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - data-volume:/data/backups/ diff --git a/systest/backup/advanced-scenarios/acl-nonAcl/docker-compose.yml b/systest/backup/advanced-scenarios/acl-nonAcl/docker-compose.yml index d8ee305c39f..1581ec68737 100755 --- a/systest/backup/advanced-scenarios/acl-nonAcl/docker-compose.yml +++ b/systest/backup/advanced-scenarios/acl-nonAcl/docker-compose.yml @@ -16,7 +16,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -38,7 +38,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - data-volume:/data/backups/ @@ -59,7 +59,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - data-volume:/data/backups/ @@ -77,7 +77,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - data-volume:/data/backups/ @@ -99,7 +99,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -121,7 +121,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - data-volume:/data/backups/ @@ -142,7 +142,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - data-volume:/data/backups/ @@ -160,7 +160,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - data-volume:/data/backups/ diff --git a/systest/backup/advanced-scenarios/deleted-namespace/docker-compose.yml b/systest/backup/advanced-scenarios/deleted-namespace/docker-compose.yml index fec9ee0c03f..f3f328bf3c1 100755 --- a/systest/backup/advanced-scenarios/deleted-namespace/docker-compose.yml +++ b/systest/backup/advanced-scenarios/deleted-namespace/docker-compose.yml @@ -14,7 +14,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -36,7 +36,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - data-volume:/data/backups/ @@ -56,7 +56,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -78,7 +78,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - data-volume:/data/backups/ diff --git a/systest/backup/encryption/docker-compose.yml b/systest/backup/encryption/docker-compose.yml index 6a784c3b64b..8a6646e2451 100644 --- a/systest/backup/encryption/docker-compose.yml +++ b/systest/backup/encryption/docker-compose.yml @@ -12,7 +12,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -41,7 +41,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -70,7 +70,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -97,7 +97,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/backup/filesystem/docker-compose.yml b/systest/backup/filesystem/docker-compose.yml index ebcf7f8f821..0a89570bd58 100644 --- a/systest/backup/filesystem/docker-compose.yml +++ b/systest/backup/filesystem/docker-compose.yml @@ -12,7 +12,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -38,7 +38,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -64,7 +64,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -90,7 +90,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/backup/minio-large/docker-compose.yml b/systest/backup/minio-large/docker-compose.yml index 27668ab70c3..c48fe625ad0 100644 --- a/systest/backup/minio-large/docker-compose.yml +++ b/systest/backup/minio-large/docker-compose.yml @@ -14,7 +14,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -38,7 +38,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -62,7 +62,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -91,7 +91,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/backup/minio/docker-compose.yml b/systest/backup/minio/docker-compose.yml index 9fb1ebd3f46..6e8674bbb49 100644 --- a/systest/backup/minio/docker-compose.yml +++ b/systest/backup/minio/docker-compose.yml @@ -14,7 +14,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -39,7 +39,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -64,7 +64,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -87,7 +87,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/backup/multi-tenancy/docker-compose.yml b/systest/backup/multi-tenancy/docker-compose.yml index 0be2bb353d3..0a3111ddecf 100644 --- a/systest/backup/multi-tenancy/docker-compose.yml +++ b/systest/backup/multi-tenancy/docker-compose.yml @@ -12,7 +12,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -36,7 +36,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -60,7 +60,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -84,7 +84,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/backup/nfs-backup/docker-compose.yml b/systest/backup/nfs-backup/docker-compose.yml index 8046fe69262..9f0182c97f9 100644 --- a/systest/backup/nfs-backup/docker-compose.yml +++ b/systest/backup/nfs-backup/docker-compose.yml @@ -17,7 +17,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -51,7 +51,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -73,7 +73,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -95,7 +95,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -115,7 +115,7 @@ services: service: zero volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -136,7 +136,7 @@ services: service: zero volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -160,7 +160,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -194,7 +194,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -216,7 +216,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -238,7 +238,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -258,7 +258,7 @@ services: service: zero volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -279,7 +279,7 @@ services: service: zero volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -302,7 +302,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -324,7 +324,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -359,7 +359,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -381,7 +381,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/systest/bgindex/docker-compose.yml b/systest/bgindex/docker-compose.yml index 1b28063e43f..388752ea2e6 100644 --- a/systest/bgindex/docker-compose.yml +++ b/systest/bgindex/docker-compose.yml @@ -12,7 +12,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -32,7 +32,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/systest/bulk_live/bulk/docker-compose.yml b/systest/bulk_live/bulk/docker-compose.yml index 70d53e78286..59d11ecccba 100644 --- a/systest/bulk_live/bulk/docker-compose.yml +++ b/systest/bulk_live/bulk/docker-compose.yml @@ -19,7 +19,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/systest/bulk_live/live/docker-compose.yml b/systest/bulk_live/live/docker-compose.yml index 6a6bdc8914c..8f238f83617 100644 --- a/systest/bulk_live/live/docker-compose.yml +++ b/systest/bulk_live/live/docker-compose.yml @@ -12,7 +12,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -39,7 +39,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/systest/cdc/docker-compose.yml b/systest/cdc/docker-compose.yml index 61eae2d9d45..96a30007859 100644 --- a/systest/cdc/docker-compose.yml +++ b/systest/cdc/docker-compose.yml @@ -10,7 +10,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -29,7 +29,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/systest/cloud/docker-compose.yml b/systest/cloud/docker-compose.yml index f554abf18e5..97b23e045ac 100644 --- a/systest/cloud/docker-compose.yml +++ b/systest/cloud/docker-compose.yml @@ -11,7 +11,7 @@ services: service: zero volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -25,7 +25,7 @@ services: - ./../../dgraph/minio.env volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/export/docker-compose.yml b/systest/export/docker-compose.yml index f6e75362395..d61f05ea29e 100644 --- a/systest/export/docker-compose.yml +++ b/systest/export/docker-compose.yml @@ -14,7 +14,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: volume @@ -36,7 +36,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: volume @@ -58,7 +58,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: volume @@ -85,7 +85,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/systest/group-delete/docker-compose.yml b/systest/group-delete/docker-compose.yml index 4886f5e4ce9..ea3e6364777 100644 --- a/systest/group-delete/docker-compose.yml +++ b/systest/group-delete/docker-compose.yml @@ -12,7 +12,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -28,7 +28,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -44,7 +44,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -60,7 +60,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/systest/ldbc/docker-compose.yml b/systest/ldbc/docker-compose.yml index f38aba47e21..ffa4dc44d1f 100644 --- a/systest/ldbc/docker-compose.yml +++ b/systest/ldbc/docker-compose.yml @@ -10,7 +10,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/systest/loader-benchmark/docker-compose.yml b/systest/loader-benchmark/docker-compose.yml index a060688b59b..0eba2b04767 100644 --- a/systest/loader-benchmark/docker-compose.yml +++ b/systest/loader-benchmark/docker-compose.yml @@ -13,7 +13,7 @@ services: cluster: test volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -26,7 +26,7 @@ services: working_dir: /data/dg1 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: volume diff --git a/systest/loader/docker-compose.yml b/systest/loader/docker-compose.yml index 7c2b8c2a9f6..a83c5951eae 100644 --- a/systest/loader/docker-compose.yml +++ b/systest/loader/docker-compose.yml @@ -12,7 +12,7 @@ services: - "9080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -34,7 +34,7 @@ services: - "6080" volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/multi-tenancy/docker-compose.yml b/systest/multi-tenancy/docker-compose.yml index e46aa164106..d79d747fb6d 100644 --- a/systest/multi-tenancy/docker-compose.yml +++ b/systest/multi-tenancy/docker-compose.yml @@ -11,7 +11,7 @@ services: service: zero volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: @@ -23,7 +23,7 @@ services: working_dir: /data/alpha1 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/online-restore/docker-compose.yml b/systest/online-restore/docker-compose.yml index 06ef95dd743..51a69f056ed 100644 --- a/systest/online-restore/docker-compose.yml +++ b/systest/online-restore/docker-compose.yml @@ -10,7 +10,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -47,7 +47,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -84,7 +84,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -121,7 +121,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -158,7 +158,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -195,7 +195,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -230,7 +230,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind diff --git a/systest/plugin/docker-compose.yml b/systest/plugin/docker-compose.yml index cfe024b17d6..8d6850e3277 100644 --- a/systest/plugin/docker-compose.yml +++ b/systest/plugin/docker-compose.yml @@ -12,7 +12,7 @@ services: - 9080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true - type: bind @@ -33,7 +33,7 @@ services: - 6080 volumes: - type: bind - source: $GOPATH/bin + source: ${LINUX_GOBIN:-$GOPATH/bin} target: /gobin read_only: true command: diff --git a/t/Makefile b/t/Makefile index 618c30c4d97..14ed4d61d68 100644 --- a/t/Makefile +++ b/t/Makefile @@ -5,30 +5,63 @@ # linux || darwin GOOS ?= $(shell go env GOOS) -GOPATH ?= $(shell go env GOPATH) -MIN_GO_VERSION = "1.24.3" +export GOPATH ?= $(shell go env GOPATH) +GOHOSTOS := $(shell go env GOHOSTOS) +GOHOSTARCH := $(shell go env GOHOSTARCH) + +# On non-Linux systems, use a separate directory for Linux binaries +ifeq ($(GOHOSTOS),linux) + export LINUX_GOBIN ?= $(GOPATH)/bin +else + export LINUX_GOBIN ?= $(GOPATH)/linux_$(GOHOSTARCH) +endif all: test .PHONY: check -check: - @which go > /dev/null 2>&1 || (echo "Error: Go is not installed or not in PATH" && exit 1) - @go version | awk '{split($$3,v,"go"); if(v[2] < $(MIN_GO_VERSION)) {print "Error: Go version must be $(MIN_GO_VERSION) or higher"; exit 1}}' - @which docker > /dev/null 2>&1 || (echo "Error: Docker is not installed or not in PATH" && exit 1) - @which gotestsum > /dev/null 2>&1 || (echo "Error: gotestsum is not installed or not in PATH" && exit 1) +check: check-go check-docker check-gotestsum check-ack @if [ "$(GOOS)" = "linux" ]; then \ which protoc > /dev/null 2>&1 || (echo "Error: protoc is not installed or not in PATH" && exit 1); \ fi @echo "All dependencies are installed" - @if [ -f "$(GOPATH)/bin/dgraph" ]; then \ - file $(GOPATH)/bin/dgraph | grep -q "ELF.*executable" || (echo "Error: dgraph binary is not a Linux executable" && exit 1); \ + @echo "LINUX_GOBIN=$(LINUX_GOBIN)" + @if [ -f "$(LINUX_GOBIN)/dgraph" ]; then \ + file $(LINUX_GOBIN)/dgraph | grep -q "ELF.*executable" || (echo "Error: dgraph binary at $(LINUX_GOBIN)/dgraph is not a Linux executable" && exit 1); \ else \ - echo "Error: dgraph binary not found in $(GOPATH)/bin" && exit 1; \ + echo "Error: dgraph binary not found at $(LINUX_GOBIN)/dgraph" && exit 1; \ fi @echo "The dgraph binary is a Linux executable (as required)" +.PHONY: check-docker +check-docker: + @./scripts/check-docker.sh + +.PHONY: check-gotestsum +check-gotestsum: + @./scripts/check-gotestsum.sh + +.PHONY: check-ack +check-ack: + @./scripts/check-ack.sh + +.PHONY: check-protoc +check-protoc: + @./scripts/check-protoc.sh + +.PHONY: check-go +check-go: + @./scripts/check-go.sh + +.PHONY: check-brew +check-brew: + @./scripts/check-brew.sh + +.PHONY: dgraph-installed +dgraph-installed: + @$(MAKE) -C .. dgraph-installed + .PHONY: test -test: check +test: dgraph-installed check # build the t.go binary @go build . # clean go testcache diff --git a/t/README.md b/t/README.md index 1391e913925..b97b1604e1d 100644 --- a/t/README.md +++ b/t/README.md @@ -1,13 +1,13 @@ # Dgraph Testing Framework -Dgraph employs a ~~complex~~ sophisticated testing framework that includes extensive test coverage. -Due to the comprehensive nature of these tests, a complete test run can take several hours, -depending on your hardware. To manage this complex testing process efficiently, we've developed a -custom test framework implemented in Go: [t.go](t.go). This specialized runner provides enhanced -control and flexibility beyond what's available through the standard Go testing framework. +> 📖 For a comprehensive guide to all Dgraph testing approaches (unit tests, build tags, test +> conventions), see [TESTING.md](../TESTING.md) in the repository root. -**Note:** This testing framework was built with Linux in mind. Non-Linux testing _can_ be -achieved—see [Running tests on OSX](#running-tests-on-osx) below. +Dgraph employs a sophisticated testing framework that includes extensive test coverage. Due to the +comprehensive nature of these tests, a complete test run can take several hours, depending on your +hardware. To manage this complex testing process efficiently, we've developed a custom test +framework implemented in Go: [t.go](t.go). This specialized runner provides enhanced control and +flexibility beyond what's available through the standard Go testing framework. ## Requirements @@ -43,15 +43,9 @@ Or, `sudo apt update && sudo apt install -y protobuf-compiler`. ## Running Tests -The tests use the Dgraph binary found at $(GOPATH)/bin/dgraph. To check your current $GOPATH: -`go env GOPATH`. - ---- - -Use the `make install` target in the top-level Makefile to build a binary with your changes that -need testing. Note for non-Linux users: because the binary is run in the Docker environment, it -needs to be a valid Linux executable. See the section below on -[Running tests on OSX](#running-tests-on-osx). +Use the `make install` target in the top-level Makefile to build a binary with your changes. On +non-Linux systems (macOS), this automatically builds both a native binary and a Linux binary for +Docker-based tests. First, build the `t` program if you haven't already: @@ -114,50 +108,23 @@ unique Docker configurations not covered by existing compose files, you should c directory with your tests also containing a custom docker-compose.yml file tailored to your specific testing requirements. -## Running tests on OSX +## Running tests on macOS -The testing framework works well on Linux systems. Some additional steps need to be taken for the -tests to run on OSX. +The build system automatically handles cross-compilation. When you run `make install` on macOS, it +builds both: -### Install location +- A native macOS binary at `$GOPATH/bin/dgraph` +- A Linux binary at `$GOPATH/linux_/dgraph` (used by Docker tests) -The Docker environment used to perform integration testing uses the dgraph binary found in -$GOPATH/bin. This binary is required to be a GOOS=linux image. The following commands need to be run -prior to starting tests to ensure the appropriate images are in place. Note, if your GOPATH variable -is not set, run ``export GOPATH=`go env GOPATH` `` +No additional setup is required. Just run: ```sh -cd .. -# builds the OSX version make install -mv $GOPATH/bin/dgraph $GOPATH/bin/dgraph_osx -# builds the linux version, take note of where the target reports it has written the dgraph executable -GOOS=linux make install -mv $GOPATH/bin/linux_arm64/dgraph $GOPATH/bin/dgraph -cd t -make check +cd t && make check && go build . +./t --pkg= ``` -The following environment variables are needed when tests are executed: - -- GOPATH - needed to map the Dgraph image in Docker environments -- DGRAPH_BINARY - the system-native (OSX) dgraph image, used by some tests not in the Docker - environment -- DOCKER_HOST - on newer Docker Desktop versions, the Docker communications socket was moved to your - home folder - -Example: - -```sh -export GOPATH=`go env GOPATH` -export DGRAPH_BINARY=$GOPATH/bin/dgraph_osx -export DOCKER_HOST=unix://${HOME}/.docker/run/docker.sock -``` - -At this point, the `t` executable can be run as described above. - -### Common Pitfalls +### Troubleshooting -If you see `exec format error` output from test runs, it is most likely because some tests attempt -to run the Dgraph image copied from the filesystem in the Docker environment. This is a known issue -with some integration tests. +If tests fail to start, run `make check` to verify all dependencies are installed and the dgraph +binaries are properly built. diff --git a/t/scripts/check-ack.sh b/t/scripts/check-ack.sh new file mode 100755 index 00000000000..de7217238f1 --- /dev/null +++ b/t/scripts/check-ack.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2310,SC2329 +set -euo pipefail + +# shellcheck source=checkhelper.sh +source "$(dirname "${BASH_SOURCE[0]}")/checkhelper.sh" + +# Find ack binary (may be 'ack' or 'ack-grep' on older Debian/Ubuntu) +find_ack() { + command -v ack 2>/dev/null || command -v ack-grep 2>/dev/null +} + +install_ack_linux() { + if command -v apt-get &>/dev/null; then + sudo apt-get update + # Try 'ack' first (newer distros), fall back to 'ack-grep' (older distros) + if apt-cache show ack &>/dev/null; then + sudo apt-get install -y ack + else + sudo apt-get install -y ack-grep + fi + else + # dnf/yum/pacman all use 'ack' + install_linux_pkg "ack" + fi +} + +install_ack_macos() { + ensure_brew || exit 1 + brew install ack +} + +install_ack() { + run_os_installer install_ack_linux install_ack_macos +} + +main() { + if find_ack &>/dev/null; then + exit 0 + fi + + if [[ ${AUTO_INSTALL-} == "true" ]]; then + install_ack + if find_ack &>/dev/null; then + exit 0 + else + err "ack check still failing after installation" + exit 1 + fi + fi + + echo "" + err "ack is not installed" + echo "" + err "Please install ack manually:" + + case "$(get_os)" in + Linux) + err " apt: sudo apt-get install ack (or ack-grep on older systems)" + err " dnf: sudo dnf install ack" + err " yum: sudo yum install ack" + err " pacman: sudo pacman -S ack" + ;; + Darwin) + err " brew install ack" + ;; + *) + err " (see https://beyondgrep.com/install/)" + ;; + esac + + print_auto_install_hint + exit 1 +} + +main "$@" diff --git a/t/scripts/check-brew.sh b/t/scripts/check-brew.sh new file mode 100755 index 00000000000..88ad35b63e8 --- /dev/null +++ b/t/scripts/check-brew.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=checkhelper.sh +source "$(dirname "${BASH_SOURCE[0]}")/checkhelper.sh" + +# If run directly, just check/install brew +ensure_brew diff --git a/t/scripts/check-docker.sh b/t/scripts/check-docker.sh new file mode 100755 index 00000000000..62bfcfb40a8 --- /dev/null +++ b/t/scripts/check-docker.sh @@ -0,0 +1,236 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1091,SC2310,SC2312,SC2329 +set -euo pipefail + +# shellcheck source=checkhelper.sh +source "$(dirname "${BASH_SOURCE[0]}")/checkhelper.sh" + +# ---- thresholds ---- +MIN_DOCKER_MAJOR=29 + +MIN_COMPOSE_MAJOR=2 +MIN_COMPOSE_MINOR=40 +MIN_COMPOSE_PATCH=0 + +MIN_MEM_MB=4 +REC_MEM_MB=8 + +# Compare semver: returns 0 if a >= b +semver_ge() { + local aMaj=$1 aMin=$2 aPat=$3 bMaj=$4 bMin=$5 bPat=$6 + ((aMaj > bMaj)) && return 0 + ((aMaj < bMaj)) && return 1 + ((aMin > bMin)) && return 0 + ((aMin < bMin)) && return 1 + ((aPat >= bPat)) +} + +# Find docker binary +find_docker() { + command -v docker 2>/dev/null +} + +install_docker_linux() { + if command -v apt-get &>/dev/null; then + # Install prerequisites + sudo apt-get update + sudo apt-get install -y ca-certificates curl gnupg + + # Add Docker's official GPG key + sudo install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + sudo chmod a+r /etc/apt/keyrings/docker.gpg + + # Detect distro (works for Ubuntu and Debian) + local distro + if [[ -f /etc/os-release ]]; then + # shellcheck source=/dev/null + . /etc/os-release + distro="${ID:-ubuntu}" + else + distro="ubuntu" + fi + + # Add the repository + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${distro} \ + $(. /etc/os-release && echo "${VERSION_CODENAME:-$(lsb_release -cs)}") stable" | + sudo tee /etc/apt/sources.list.d/docker.list >/dev/null + + sudo apt-get update + sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + + # Start and enable Docker service + sudo systemctl start docker || true + sudo systemctl enable docker || true + + elif command -v dnf &>/dev/null; then + sudo dnf -y install dnf-plugins-core + sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo + sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + sudo systemctl start docker || true + sudo systemctl enable docker || true + + elif command -v yum &>/dev/null; then + sudo yum install -y yum-utils + sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo + sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + sudo systemctl start docker || true + sudo systemctl enable docker || true + + elif command -v pacman &>/dev/null; then + sudo pacman -S --noconfirm docker docker-compose + sudo systemctl start docker || true + sudo systemctl enable docker || true + + else + err "no supported package manager found (tried: apt, dnf, yum, pacman)" + exit 1 + fi + + # Add current user to docker group to avoid needing sudo + if [[ -n ${SUDO_USER-} ]]; then + sudo usermod -aG docker "${SUDO_USER}" || true + elif [[ -n ${USER-} ]] && [[ ${USER} != "root" ]]; then + sudo usermod -aG docker "${USER}" || true + fi +} + +install_docker_macos() { + ensure_brew || exit 1 + brew install --cask docker +} + +install_docker() { + run_os_installer install_docker_linux install_docker_macos +} + +print_install_instructions() { + echo "" + err "Docker is not installed" + echo "" + err "Please install Docker manually:" + + case "$(get_os)" in + Linux) + err " apt (Ubuntu/Debian): see https://docs.docker.com/engine/install/ubuntu/" + err " dnf (Fedora): see https://docs.docker.com/engine/install/fedora/" + err " yum (CentOS/RHEL): see https://docs.docker.com/engine/install/centos/" + err " pacman (Arch): sudo pacman -S docker docker-compose" + ;; + Darwin) + err " brew install --cask docker" + err " Or download from: https://www.docker.com/products/docker-desktop" + ;; + *) + err " See: https://docs.docker.com/get-docker/" + ;; + esac + + print_auto_install_hint +} + +check_docker_requirements() { + # Check jq dependency for version parsing + if ! command -v jq &>/dev/null; then + err "jq not found in PATH (required for version parsing)" + exit 1 + fi + + # Check Docker daemon is running + if ! docker info &>/dev/null; then + err "Docker daemon is not running" + err "Please start Docker Desktop or the Docker service" + exit 1 + fi + + # Fetch all info in one call + local docker_info + if ! docker_info="$(docker info --format '{{json .}}' 2>&1)"; then + err "failed to get docker info: ${docker_info}" + exit 1 + fi + + # 1) Parse and check Docker version + local docker_ver docker_maj docker_min docker_pat + docker_ver="$(jq -r '.ServerVersion // empty' <<<"${docker_info}")" + if [[ -z ${docker_ver} ]]; then + err "could not get ServerVersion from docker info" + exit 1 + fi + IFS='.' read -r docker_maj docker_min docker_pat <<<"${docker_ver%%-*}" + docker_pat="${docker_pat:-0}" + + if ((docker_maj < MIN_DOCKER_MAJOR)); then + err "Docker version ${docker_maj}.${docker_min}.${docker_pat} is below minimum (need >= ${MIN_DOCKER_MAJOR})" + exit 1 + fi + + # 2) Parse and check Docker Compose version + local compose_ver compose_maj compose_min compose_pat + compose_ver="$(jq -r '.ClientInfo.Plugins[] | select(.Name == "compose") | .Version // empty' <<<"${docker_info}" | sed 's/^v//')" + if [[ -z ${compose_ver} ]]; then + err "Docker Compose plugin not found (is Compose v2 installed?)" + exit 1 + fi + IFS='.' read -r compose_maj compose_min compose_pat <<<"${compose_ver%%-*}" + compose_pat="${compose_pat:-0}" + + if ! semver_ge "${compose_maj}" "${compose_min}" "${compose_pat}" \ + "${MIN_COMPOSE_MAJOR}" "${MIN_COMPOSE_MINOR}" "${MIN_COMPOSE_PATCH}"; then + err "Docker Compose ${compose_maj}.${compose_min}.${compose_pat} is below minimum (need >= ${MIN_COMPOSE_MAJOR}.${MIN_COMPOSE_MINOR}.${MIN_COMPOSE_PATCH})" + exit 1 + fi + + # 3) Check memory + local mem_bytes mem_mb + mem_bytes="$(jq -r '.MemTotal // empty' <<<"${docker_info}")" + if [[ -z ${mem_bytes} || ! ${mem_bytes} =~ ^[0-9]+$ ]]; then + err "could not get MemTotal from docker info" + exit 1 + fi + mem_mb=$((mem_bytes / 1024 / 1024)) + + if ((mem_mb < MIN_MEM_MB)); then + err "Docker memory ${mem_mb}MB is below minimum (need >= ${MIN_MEM_MB}MB)" + exit 1 + fi + if ((mem_mb < REC_MEM_MB)); then + warn "Docker memory ${mem_mb}MB is below recommended (>= ${REC_MEM_MB}MB)" + fi +} + +main() { + # Check if docker is already available + if find_docker &>/dev/null; then + check_docker_requirements + exit 0 + fi + + # Docker not found + if [[ ${AUTO_INSTALL-} == "true" ]]; then + install_docker + + # Re-check after install + if find_docker &>/dev/null; then + # On macOS, the daemon might not be running yet after cask install + if docker info &>/dev/null; then + check_docker_requirements + else + err "Docker installed but daemon not running yet" + err "Please start Docker Desktop and re-run this script" + exit 1 + fi + exit 0 + else + err "docker check still failing after installation" + exit 1 + fi + fi + + # No auto-install, fail with instructions + print_install_instructions + exit 1 +} + +main "$@" diff --git a/t/scripts/check-go.sh b/t/scripts/check-go.sh new file mode 100755 index 00000000000..aa764d6f9b5 --- /dev/null +++ b/t/scripts/check-go.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2310,SC2329 +set -euo pipefail + +# shellcheck source=checkhelper.sh +source "$(dirname "${BASH_SOURCE[0]}")/checkhelper.sh" + +# Parse required Go version from go.mod +get_required_go_version() { + local repo_root + repo_root="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel 2>/dev/null)" || { + err "could not find git repository root" + return 1 + } + + local go_mod="${repo_root}/go.mod" + if [[ ! -f ${go_mod} ]]; then + err "go.mod not found at ${go_mod}" + return 1 + fi + + # Extract version from "go X.Y.Z" line + local version + version=$(grep -E '^go [0-9]+\.[0-9]+' "${go_mod}" | awk '{print $2}') + if [[ -z ${version} ]]; then + err "could not parse go version from go.mod" + return 1 + fi + + echo "${version}" +} + +MIN_GO_VERSION="$(get_required_go_version)" || exit 1 + +find_go() { + command -v go 2>/dev/null +} + +# Check if installed version meets minimum requirement +version_ok() { + local current="$1" + local required="${MIN_GO_VERSION}" + local lowest + lowest=$(printf '%s\n%s\n' "${current}" "${required}" | sort -V | head -n1) + [[ ${lowest} == "${required}" ]] +} + +install_go_linux() { + local version="${MIN_GO_VERSION}" + local arch + arch="$(dpkg --print-architecture 2>/dev/null || uname -m)" + + case "${arch}" in + x86_64 | amd64) arch="amd64" ;; + aarch64 | arm64) arch="arm64" ;; + *) + err "unsupported architecture: ${arch}" + exit 1 + ;; + esac + + local tarball="go${version}.linux-${arch}.tar.gz" + local url="https://go.dev/dl/${tarball}" + + curl -fsSL "${url}" -o "/tmp/${tarball}" + sudo rm -rf /usr/local/go + sudo tar -C /usr/local -xzf "/tmp/${tarball}" + rm "/tmp/${tarball}" + + export PATH="/usr/local/go/bin:${PATH}" +} + +install_go_macos() { + ensure_brew || exit 1 + brew install go +} + +install_go() { + run_os_installer install_go_linux install_go_macos +} + +check_go_version() { + local go_bin="$1" + local version_out + version_out="$("${go_bin}" version 2>/dev/null)" + + if [[ ${version_out} =~ go([0-9]+\.[0-9]+\.?[0-9]*) ]]; then + local current="${BASH_REMATCH[1]}" + if version_ok "${current}"; then + return 0 + else + err "Go version ${current} is below minimum required (${MIN_GO_VERSION})" + return 1 + fi + else + err "could not parse Go version from: ${version_out}" + return 1 + fi +} + +main() { + local go_bin + if go_bin="$(find_go)" && check_go_version "${go_bin}"; then + exit 0 + fi + + if [[ ${AUTO_INSTALL-} == "true" ]]; then + install_go + if go_bin="$(find_go)" && check_go_version "${go_bin}"; then + exit 0 + else + err "go check still failing after installation" + exit 1 + fi + fi + + echo "" + if find_go &>/dev/null; then + err "Go is installed but version is below minimum required (${MIN_GO_VERSION})" + else + err "Go is not installed" + fi + echo "" + err "Please install Go ${MIN_GO_VERSION} or higher:" + + case "$(get_os)" in + Linux) + err " See: https://go.dev/doc/install" + ;; + Darwin) + err " brew install go" + err " Or download from: https://go.dev/doc/install" + ;; + *) + err " See: https://go.dev/doc/install" + ;; + esac + + print_auto_install_hint + exit 1 +} + +main "$@" diff --git a/t/scripts/check-gotestsum.sh b/t/scripts/check-gotestsum.sh new file mode 100755 index 00000000000..d5cc914927f --- /dev/null +++ b/t/scripts/check-gotestsum.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2310 +set -euo pipefail + +# shellcheck source=checkhelper.sh +source "$(dirname "${BASH_SOURCE[0]}")/checkhelper.sh" + +MIN_MAJOR=1 +MIN_MINOR=13 +INSTALL_CMD="go install gotest.tools/gotestsum@latest" + +# Returns 0 if a.b >= min_major.min_minor +version_ok() { + local maj=$1 min=$2 + ((maj > MIN_MAJOR)) && return 0 + ((maj < MIN_MAJOR)) && return 1 + ((min >= MIN_MINOR)) +} + +check_gotestsum() { + local gopath="${GOPATH:-${HOME}/go}" + local gotestsum_bin="${gopath}/bin/gotestsum" + + if [[ ! -x ${gotestsum_bin} ]]; then + return 1 + fi + + local version_out maj min + if ! version_out="$("${gotestsum_bin}" --version 2>/dev/null)"; then + return 1 + fi + + # Parse version (e.g., "gotestsum version 1.12.0" or just "1.12.0") + if [[ ${version_out} =~ ([0-9]+)\.([0-9]+) ]]; then + maj="${BASH_REMATCH[1]}" + min="${BASH_REMATCH[2]}" + else + return 1 + fi + + if ! version_ok "${maj}" "${min}"; then + warn "gotestsum ${maj}.${min} is below minimum required version ${MIN_MAJOR}.${MIN_MINOR}" + return 1 + fi + + return 0 +} + +install_gotestsum() { + if ! ${INSTALL_CMD}; then + err "failed to install gotestsum" + exit 1 + fi +} + +main() { + # Check go is available + if ! command -v go &>/dev/null; then + err "go not found in PATH" + exit 1 + fi + + local gopath="${GOPATH:-${HOME}/go}" + local gotestsum_bin="${gopath}/bin/gotestsum" + + # First check + if check_gotestsum; then + exit 0 + fi + + # Determine reason for failure + local reason + if [[ ! -x ${gotestsum_bin} ]]; then + reason="gotestsum is not installed at ${gotestsum_bin}" + else + reason="gotestsum version is below minimum required (${MIN_MAJOR}.${MIN_MINOR})" + fi + + # Auto-install if AUTO_INSTALL=true + if [[ ${AUTO_INSTALL-} == "true" ]]; then + install_gotestsum + + # Re-check after install + if check_gotestsum; then + exit 0 + else + err "gotestsum check still failing after installation" + exit 1 + fi + fi + + # No auto-install, fail with instructions + echo "" + err "${reason}" + echo "" + err "Please install or upgrade gotestsum by running:" + err " ${INSTALL_CMD}" + print_auto_install_hint + exit 1 +} + +main "$@" diff --git a/t/scripts/check-protoc.sh b/t/scripts/check-protoc.sh new file mode 100755 index 00000000000..8e917b7e49b --- /dev/null +++ b/t/scripts/check-protoc.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2310,SC2329 +set -euo pipefail + +# shellcheck source=checkhelper.sh +source "$(dirname "${BASH_SOURCE[0]}")/checkhelper.sh" + +find_protoc() { + command -v protoc 2>/dev/null +} + +install_protoc_linux() { + # apt/dnf/yum use protobuf-compiler, pacman uses protobuf + install_linux_pkg "protobuf-compiler" "protobuf-compiler" "protobuf-compiler" "protobuf" +} + +install_protoc_macos() { + ensure_brew || exit 1 + brew install protobuf +} + +install_protoc() { + run_os_installer install_protoc_linux install_protoc_macos +} + +main() { + if find_protoc &>/dev/null; then + exit 0 + fi + + if [[ ${AUTO_INSTALL-} == "true" ]]; then + install_protoc + if find_protoc &>/dev/null; then + exit 0 + else + err "protoc check still failing after installation" + exit 1 + fi + fi + + echo "" + err "protoc is not installed" + echo "" + err "Please install protoc manually:" + + case "$(get_os)" in + Linux) + err " apt: sudo apt-get install protobuf-compiler" + err " dnf: sudo dnf install protobuf-compiler" + err " yum: sudo yum install protobuf-compiler" + err " pacman: sudo pacman -S protobuf" + ;; + Darwin) + err " brew install protobuf" + ;; + *) + err " See: https://grpc.io/docs/protoc-installation/" + ;; + esac + + print_auto_install_hint + exit 1 +} + +main "$@" diff --git a/t/scripts/checkhelper.sh b/t/scripts/checkhelper.sh new file mode 100755 index 00000000000..12377bffd5a --- /dev/null +++ b/t/scripts/checkhelper.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2312 +# Common functions for check scripts +# This is a library sourced by other scripts - functions are invoked indirectly + +err() { printf "ERROR: %s\n" "$*" >&2; } +warn() { printf "WARN: %s\n" "$*" >&2; } + +# Get the directory containing the scripts +# trunk-ignore(shellcheck/SC2034) +SCRIPTS_DIR="$(dirname "${BASH_SOURCE[0]}")" + +# Detect OS (Linux, Darwin, etc.) +get_os() { + uname -s +} + +# Install a package using the appropriate Linux package manager +# Usage: install_linux_pkg [] [] [] +# If only one argument is provided, it's used for all package managers +install_linux_pkg() { + local apt_pkg="${1}" + local dnf_pkg="${2:-${apt_pkg}}" + local yum_pkg="${3:-${dnf_pkg}}" + local pacman_pkg="${4:-${yum_pkg}}" + + if command -v apt-get &>/dev/null; then + sudo apt-get update + sudo apt-get install -y "${apt_pkg}" + elif command -v dnf &>/dev/null; then + sudo dnf install -y "${dnf_pkg}" + elif command -v yum &>/dev/null; then + sudo yum install -y "${yum_pkg}" + elif command -v pacman &>/dev/null; then + sudo pacman -S --noconfirm "${pacman_pkg}" + else + err "no supported package manager found (tried: apt, dnf, yum, pacman)" + return 1 + fi +} + +# Ensure Homebrew is installed and available (macOS) +ensure_brew() { + if command -v brew &>/dev/null; then + return 0 + fi + + if [[ ${AUTO_INSTALL-} != "true" ]]; then + err "Homebrew is not installed" + err "Please install Homebrew: https://brew.sh" + err "Or re-run with AUTO_INSTALL=true" + return 1 + fi + + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + # Extend PATH to find freshly installed brew, then source shellenv + export PATH="/opt/homebrew/bin:/usr/local/bin:${PATH}" + if command -v brew &>/dev/null; then + eval "$("$(command -v brew)" shellenv)" + else + err "Homebrew installation failed" + return 1 + fi +} + +# Print the common "re-run with AUTO_INSTALL" message +print_auto_install_hint() { + echo "" + err "Or re-run with AUTO_INSTALL=true to install automatically." +} + +# Run OS-specific installer +# Usage: run_os_installer +run_os_installer() { + local linux_func="$1" + local macos_func="$2" + local os + os="$(get_os)" + + case "${os}" in + Linux) + "${linux_func}" + ;; + Darwin) + "${macos_func}" + ;; + *) + err "unsupported operating system: ${os}" + return 1 + ;; + esac +} diff --git a/t/t.go b/t/t.go index be005a1aa75..b6dda3e46ac 100644 --- a/t/t.go +++ b/t/t.go @@ -169,11 +169,64 @@ func command(args ...string) *exec.Cmd { return commandWithContext(ctxb, args...) } +// ensureGoPathLinuxBinEnvVarSet sets LINUX_GOBIN environment variable if not already set. +// On Linux, it defaults to $GOPATH/bin. On other systems (macOS, etc.), it defaults +// to $GOPATH/linux_$GOARCH/bin to support cross-compiled Linux binaries for Docker tests. +func ensureGoPathLinuxBinEnvVarSet() { + if os.Getenv("LINUX_GOBIN") != "" { + return // already set + } + + gopath := os.Getenv("GOPATH") + if gopath == "" { + gopath = filepath.Join(os.Getenv("HOME"), "go") + } + + var gopathLinuxBin string + if runtime.GOOS == "linux" { + gopathLinuxBin = filepath.Join(gopath, "bin") + } else { + gopathLinuxBin = filepath.Join(gopath, "linux_"+runtime.GOARCH) + } + os.Setenv("LINUX_GOBIN", gopathLinuxBin) +} + +// ensureDgraphLinuxBinary checks if the dgraph binary exists under LINUX_GOBIN. +// If not found, it runs make install to build it. +func ensureDgraphLinuxBinary() error { + ensureGoPathLinuxBinEnvVarSet() + gopathLinuxBin := os.Getenv("LINUX_GOBIN") + dgraphBin := filepath.Join(gopathLinuxBin, "dgraph") + + if _, err := os.Stat(dgraphBin); err == nil { + return nil // binary exists + } + + fmt.Printf("Dgraph binary not found at %s, building...\n", dgraphBin) + var cmd *exec.Cmd + if *race { + cmd = command("make", "BUILD_RACE=y", "install") + } else { + cmd = command("make", "install") + } + cmd.Dir = *baseDir + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to build dgraph binary: %w", err) + } + fmt.Printf("Dgraph binary built successfully at %s\n", dgraphBin) + return nil +} + func startCluster(composeFile, prefix string) error { + ensureGoPathLinuxBinEnvVarSet() if os.Getenv("GOPATH") == "" { return fmt.Errorf("GOPATH environment variable is required but not set") } + + if err := ensureDgraphLinuxBinary(); err != nil { + return err + } cmd := command( "docker", "compose", "--compatibility", "-f", composeFile, "-p", prefix, "up", "--force-recreate", "--build", "--remove-orphans", "--detach")