diff --git a/.agents/coding-style.md b/.agents/coding-style.md index 6ead2ef98..70cd25eee 100644 --- a/.agents/coding-style.md +++ b/.agents/coding-style.md @@ -48,6 +48,8 @@ All Go tests — including backend tests — must use [Ginkgo](https://onsi.gith Do not mix styles within a package. If you are extending tests in a package that already uses Ginkgo, keep using Ginkgo. If you find stdlib-style Go tests in the tree, treat them as tech debt to be migrated rather than as a pattern to follow. +This is enforced by `golangci-lint` via the `forbidigo` linter (see `.golangci.yml`); calls like `t.Errorf` / `t.Fatalf` / `t.Run` / `t.Skip` / `t.Logf` are flagged. Run `make lint` locally before submitting; the same check runs in CI (`.github/workflows/lint.yml`). + ## Documentation The project documentation is located in `docs/content`. When adding new features or changing existing functionality, it is crucial to update the documentation to reflect these changes. This helps users understand how to use the new capabilities and ensures the documentation stays relevant. diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..fb7797b4d --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,48 @@ +--- +name: 'lint' + +on: + pull_request: + paths-ignore: + - 'docs/**' + - 'examples/**' + - 'README.md' + - '**/*.md' + push: + branches: + - master + +concurrency: + group: ci-lint-${{ github.head_ref || github.ref }}-${{ github.repository }} + cancel-in-progress: true + +jobs: + golangci-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # Full history so golangci-lint's new-from-merge-base can reach + # origin/master and compute the diff against it. + fetch-depth: 0 + - uses: actions/setup-go@v5 + with: + go-version: '1.26.x' + cache: false + - name: install golangci-lint + run: | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ + | sh -s -- -b "$(go env GOPATH)/bin" v2.11.4 + - name: generate grpc proto sources + # pkg/grpc/proto/*.go is generated, not checked in. Several packages + # import it, so without this step typecheck fails project-wide. + run: make protogen-go + - name: stub react-ui dist for go:embed + # core/http/app.go has //go:embed react-ui/dist/*; the glob needs at + # least one non-hidden entry to satisfy typecheck. We don't run + # `make react-ui` here because lint doesn't need the real bundle. + run: | + mkdir -p core/http/react-ui/dist + touch core/http/react-ui/dist/index.html + - name: lint + run: make lint diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000..aa82a810b --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,53 @@ +version: "2" + +# Only issues introduced relative to master are reported. Pre-existing issues +# in the codebase do not fail the lint job; they're treated as a baseline that +# can be cleaned up incrementally. New code (added lines on a branch) is held +# to the full linter set. Locally, `make lint-all` overrides this and reports +# every issue. +issues: + # origin/master because in shallow CI checkouts only the remote-tracking + # branch exists; a bare 'master' ref isn't reachable locally. + new-from-merge-base: origin/master + +linters: + default: standard + # staticcheck is noisy on this codebase (mostly QF style suggestions like + # "could use tagged switch" or "unnecessary fmt.Sprintf"). Re-enable + # selectively if a high-signal subset is identified. + disable: + - staticcheck + enable: + - forbidigo + settings: + forbidigo: + forbid: + - pattern: '^t\.Errorf$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use Expect(...).To(...) instead of t.Errorf. See .agents/coding-style.md.' + - pattern: '^t\.Error$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use Expect(...).To(...) instead of t.Error. See .agents/coding-style.md.' + - pattern: '^t\.Fatalf$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use Expect(...).To(Succeed()) / Fail(...) instead of t.Fatalf. See .agents/coding-style.md.' + - pattern: '^t\.Fatal$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use Expect(...).To(Succeed()) / Fail(...) instead of t.Fatal. See .agents/coding-style.md.' + - pattern: '^t\.Run$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use Describe/Context/It instead of t.Run. See .agents/coding-style.md.' + - pattern: '^t\.Skip$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use Skip(...) instead of t.Skip. See .agents/coding-style.md.' + - pattern: '^t\.Skipf$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use Skip(...) instead of t.Skipf. See .agents/coding-style.md.' + - pattern: '^t\.SkipNow$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use Skip(...) instead of t.SkipNow. See .agents/coding-style.md.' + - pattern: '^t\.Logf$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use GinkgoWriter / fmt.Fprintf(GinkgoWriter, ...) instead of t.Logf. See .agents/coding-style.md.' + - pattern: '^t\.Log$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use GinkgoWriter / fmt.Fprintln(GinkgoWriter, ...) instead of t.Log. See .agents/coding-style.md.' + - pattern: '^t\.Fail$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use Fail(...) instead of t.Fail. See .agents/coding-style.md.' + - pattern: '^t\.FailNow$' + msg: 'LocalAI tests must use Ginkgo/Gomega; use Fail(...) instead of t.FailNow. See .agents/coding-style.md.' + exclusions: + paths: + # Upstream whisper.cpp source tree fetched by the whisper backend Makefile. + - 'backend/go/whisper/sources' + - 'docs/' diff --git a/Makefile b/Makefile index a76f7975a..f9c2d87ef 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ endif TEST_PATHS?=./api/... ./pkg/... ./core/... -.PHONY: all test build vendor +.PHONY: all test build vendor lint lint-all all: help @@ -163,6 +163,38 @@ test: prepare-test OPUS_SHIM_LIBRARY=$(abspath ./pkg/opus/shim/libopusshim.so) \ $(GOCMD) run github.com/onsi/ginkgo/v2/ginkgo --flake-attempts $(TEST_FLAKES) --fail-fast -v -r $(TEST_PATHS) +######################################################## +## Lint +######################################################## +## Runs golangci-lint with config from .golangci.yml. Includes the standard +## linter set plus forbidigo, which enforces the Ginkgo/Gomega-only test +## convention documented in .agents/coding-style.md. +## +## LINT_EXCLUDE_DIRS_RE matches directories whose Go packages can't typecheck +## without C/C++ headers we don't install in the lint runner (cgo wrappers +## around llama.cpp, piper/spdlog, silero-vad/onnxruntime, and Fyne/OpenGL for +## the launcher). Their compile-time correctness is enforced by their own +## build pipelines. Keep this as a deny list — `go list ./...` discovers +## everything else automatically, so new packages are scanned by default. +LINT_EXCLUDE_DIRS_RE=/(backend/go/(piper|silero-vad|llm)|cmd/launcher)(/|$$) + +lint: + @command -v golangci-lint >/dev/null 2>&1 || { \ + echo 'golangci-lint not installed. Install: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest'; \ + exit 1; \ + } + golangci-lint run $$(go list -e -f '{{.Dir}}' ./... | grep -vE '$(LINT_EXCLUDE_DIRS_RE)') + +## Like `lint` but reports every issue, including the pre-existing baseline +## that `lint` ignores via .golangci.yml's new-from-merge-base. Use this to +## see what's available to clean up. +lint-all: + @command -v golangci-lint >/dev/null 2>&1 || { \ + echo 'golangci-lint not installed. Install: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest'; \ + exit 1; \ + } + golangci-lint run --new=false --new-from-merge-base= --new-from-rev= $$(go list -e -f '{{.Dir}}' ./... | grep -vE '$(LINT_EXCLUDE_DIRS_RE)') + ######################################################## ## E2E AIO tests (uses standard image with pre-configured models) ######################################################## diff --git a/backend/go/sam3-cpp/CMakeLists.txt b/backend/go/sam3-cpp/CMakeLists.txt index c43569d50..73c8e2d01 100644 --- a/backend/go/sam3-cpp/CMakeLists.txt +++ b/backend/go/sam3-cpp/CMakeLists.txt @@ -10,7 +10,7 @@ set(SAM3_BUILD_TESTS OFF CACHE BOOL "Disable sam3.cpp tests" FORCE) add_subdirectory(./sources/sam3.cpp) -add_library(gosam3 MODULE gosam3.cpp) +add_library(gosam3 MODULE cpp/gosam3.cpp) target_link_libraries(gosam3 PRIVATE sam3 ggml) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) diff --git a/backend/go/sam3-cpp/Makefile b/backend/go/sam3-cpp/Makefile index ed0aa3c6e..53b0dfb5e 100644 --- a/backend/go/sam3-cpp/Makefile +++ b/backend/go/sam3-cpp/Makefile @@ -111,7 +111,7 @@ libgosam3-fallback.so: sources/sam3.cpp SO_TARGET=libgosam3-fallback.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgosam3-custom rm -rfv build* -libgosam3-custom: CMakeLists.txt gosam3.cpp gosam3.h +libgosam3-custom: CMakeLists.txt cpp/gosam3.cpp cpp/gosam3.h mkdir -p build-$(SO_TARGET) && \ cd build-$(SO_TARGET) && \ cmake .. $(CMAKE_ARGS) && \ diff --git a/backend/go/sam3-cpp/gosam3.cpp b/backend/go/sam3-cpp/cpp/gosam3.cpp similarity index 100% rename from backend/go/sam3-cpp/gosam3.cpp rename to backend/go/sam3-cpp/cpp/gosam3.cpp diff --git a/backend/go/sam3-cpp/gosam3.h b/backend/go/sam3-cpp/cpp/gosam3.h similarity index 100% rename from backend/go/sam3-cpp/gosam3.h rename to backend/go/sam3-cpp/cpp/gosam3.h diff --git a/backend/go/stablediffusion-ggml/CMakeLists.txt b/backend/go/stablediffusion-ggml/CMakeLists.txt index 41b52c18d..35a852c31 100644 --- a/backend/go/stablediffusion-ggml/CMakeLists.txt +++ b/backend/go/stablediffusion-ggml/CMakeLists.txt @@ -4,7 +4,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_subdirectory(./sources/stablediffusion-ggml.cpp) -add_library(gosd MODULE gosd.cpp) +add_library(gosd MODULE cpp/gosd.cpp) target_link_libraries(gosd PRIVATE stable-diffusion ggml) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) diff --git a/backend/go/stablediffusion-ggml/Makefile b/backend/go/stablediffusion-ggml/Makefile index c553c0758..aef05d4e6 100644 --- a/backend/go/stablediffusion-ggml/Makefile +++ b/backend/go/stablediffusion-ggml/Makefile @@ -119,7 +119,7 @@ libgosd-fallback.so: sources/stablediffusion-ggml.cpp SO_TARGET=libgosd-fallback.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgosd-custom rm -rfv build* -libgosd-custom: CMakeLists.txt gosd.cpp gosd.h +libgosd-custom: CMakeLists.txt cpp/gosd.cpp cpp/gosd.h mkdir -p build-$(SO_TARGET) && \ cd build-$(SO_TARGET) && \ cmake .. $(CMAKE_ARGS) && \ diff --git a/backend/go/stablediffusion-ggml/gosd.cpp b/backend/go/stablediffusion-ggml/cpp/gosd.cpp similarity index 100% rename from backend/go/stablediffusion-ggml/gosd.cpp rename to backend/go/stablediffusion-ggml/cpp/gosd.cpp diff --git a/backend/go/stablediffusion-ggml/gosd.h b/backend/go/stablediffusion-ggml/cpp/gosd.h similarity index 100% rename from backend/go/stablediffusion-ggml/gosd.h rename to backend/go/stablediffusion-ggml/cpp/gosd.h diff --git a/backend/go/whisper/CMakeLists.txt b/backend/go/whisper/CMakeLists.txt index 60cc178f2..36c529006 100644 --- a/backend/go/whisper/CMakeLists.txt +++ b/backend/go/whisper/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_subdirectory(./sources/whisper.cpp) -add_library(gowhisper MODULE gowhisper.cpp) +add_library(gowhisper MODULE cpp/gowhisper.cpp) target_link_libraries(gowhisper PRIVATE whisper ggml) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) diff --git a/backend/go/whisper/Makefile b/backend/go/whisper/Makefile index c737f3b0e..e57189a81 100644 --- a/backend/go/whisper/Makefile +++ b/backend/go/whisper/Makefile @@ -111,7 +111,7 @@ libgowhisper-fallback.so: sources/whisper.cpp SO_TARGET=libgowhisper-fallback.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgowhisper-custom rm -rfv build* -libgowhisper-custom: CMakeLists.txt gowhisper.cpp gowhisper.h +libgowhisper-custom: CMakeLists.txt cpp/gowhisper.cpp cpp/gowhisper.h mkdir -p build-$(SO_TARGET) && \ cd build-$(SO_TARGET) && \ cmake .. $(CMAKE_ARGS) && \ diff --git a/backend/go/whisper/gowhisper.cpp b/backend/go/whisper/cpp/gowhisper.cpp similarity index 100% rename from backend/go/whisper/gowhisper.cpp rename to backend/go/whisper/cpp/gowhisper.cpp diff --git a/backend/go/whisper/gowhisper.h b/backend/go/whisper/cpp/gowhisper.h similarity index 100% rename from backend/go/whisper/gowhisper.h rename to backend/go/whisper/cpp/gowhisper.h diff --git a/tests/integration/integration_suite_test.go b/tests/integration/integration_suite_test.go index 0e5ab3a2c..6ed362292 100644 --- a/tests/integration/integration_suite_test.go +++ b/tests/integration/integration_suite_test.go @@ -1,7 +1,6 @@ package integration_test import ( - "os" "testing" "github.com/mudler/xlog" diff --git a/tests/integration/stores_test.go b/tests/integration/stores_test.go index 283a3876b..9b977d4c2 100644 --- a/tests/integration/stores_test.go +++ b/tests/integration/stores_test.go @@ -39,8 +39,6 @@ var _ = Describe("Integration tests for the stores backend(s) and internal APIs" BeforeEach(func() { var err error - zerolog.SetGlobalLevel(zerolog.DebugLevel) - tmpdir, err = os.MkdirTemp("", "") Expect(err).ToNot(HaveOccurred())