diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15b71523b..e1417532f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,12 @@ on: pull_request: branches: - master + workflow_dispatch: + inputs: + SKIP_ALPINE_PROVIDER_TESTS: + description: 'Skip alpine provider tests' + required: false + default: 'false' env: PACT_BROKER_BASE_URL: https://testdemo.pactflow.io @@ -75,19 +81,48 @@ jobs: if: ${{ always() }} test-containers: - runs-on: ubuntu-latest - name: ${{ matrix.go-version }}-test-container + continue-on-error: ${{ matrix.experimental }} + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }}-${{ matrix.variant }}-${{ matrix.go-version }}-test-container strategy: fail-fast: false matrix: + os: [ubuntu-latest, ubuntu-24.04-arm] go-version: ["1.22", "1.23"] + variant: [debian] + experimental: [false] + include: + - variant: alpine + go-version: 1.22 + experimental: true + os: ubuntu-latest + - variant: alpine + go-version: 1.23 + experimental: true + os: ubuntu-latest + - variant: alpine + go-version: 1.22 + experimental: true + os: ubuntu-24.04-arm + - variant: alpine + go-version: 1.23 + experimental: true + os: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 - name: Test dockerfile - run: make docker_test_all + run: | + if [[ "${{ matrix.variant }}" == "alpine" ]]; then + export SKIP_PROVIDER_TESTS=true + if [[ "${{ github.event.inputs.SKIP_ALPINE_PROVIDER_TESTS }}" == "false" ]]; then + export SKIP_PROVIDER_TESTS=false + fi + fi + make docker_test_all env: GO_VERSION: ${{ matrix.go-version }} + IMAGE_VARIANT: ${{ matrix.variant }} finish: needs: [test,test-containers] diff --git a/.gitignore b/.gitignore index dddbec92a..c98284310 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ _* *.log pact-go pacts +examples/pacts/*.json logs tmp coverage.txt diff --git a/Dockerfile.alpine b/Dockerfile.alpine new file mode 100644 index 000000000..6723ba712 --- /dev/null +++ b/Dockerfile.alpine @@ -0,0 +1,10 @@ +ARG VERSION=1.22 +FROM golang:${VERSION}-alpine + +RUN apk add --no-cache curl gcc musl-dev gzip openjdk17-jre bash protoc protobuf-dev make file + +COPY . /go/src/github.com/pact-foundation/pact-go + +WORKDIR /go/src/github.com/pact-foundation/pact-go + +CMD ["make", "test"] \ No newline at end of file diff --git a/Dockerfile b/Dockerfile.debian similarity index 100% rename from Dockerfile rename to Dockerfile.debian diff --git a/Makefile b/Makefile index 7f90ddbb2..b50ec44af 100755 --- a/Makefile +++ b/Makefile @@ -4,18 +4,23 @@ TEST?=./... .DEFAULT_GOAL := ci DOCKER_HOST_HTTP?="http://host.docker.internal" PACT_CLI="docker run --rm -v ${PWD}:${PWD} -e PACT_BROKER_BASE_URL=$(DOCKER_HOST_HTTP) -e PACT_BROKER_USERNAME -e PACT_BROKER_PASSWORD pactfoundation/pact-cli" -PLUGIN_PACT_PROTOBUF_VERSION=0.3.15 +PLUGIN_PACT_PROTOBUF_VERSION=0.5.4 PLUGIN_PACT_CSV_VERSION=0.0.6 PLUGIN_PACT_MATT_VERSION=0.1.1 PLUGIN_PACT_AVRO_VERSION=0.0.6 GO_VERSION?=1.22 +IMAGE_VARIANT?=debian ci:: docker deps clean bin test pact PACT_DOWNLOAD_DIR=/tmp ifeq ($(OS),Windows_NT) PACT_DOWNLOAD_DIR=$$TMP endif - +SKIP_RACE?=false +RACE?=-race +ifeq ($(SKIP_RACE),true) + RACE= +endif # Run the ci target from a developer machine with the environment variables # set as if it was on Travis CI. # Use this for quick feedback when playing around with your workflows. @@ -37,24 +42,32 @@ docker: docker compose up -d docker_build: - docker build -f Dockerfile --build-arg GO_VERSION=${GO_VERSION} -t pactfoundation/pact-go-test . + docker build -f Dockerfile.$(IMAGE_VARIANT) --build-arg GO_VERSION=${GO_VERSION} -t pactfoundation/pact-go-test-$(IMAGE_VARIANT) . + docker_test: docker_build docker run \ -e LOG_LEVEL=INFO \ + -e SKIP_PROVIDER_TESTS=$(SKIP_PROVIDER_TESTS) \ + -e SKIP_RACE=$(SKIP_RACE) \ --rm \ - pactfoundation/pact-go-test \ + -it \ + pactfoundation/pact-go-test-$(IMAGE_VARIANT) \ /bin/sh -c "make test" docker_pact: docker_build docker run \ -e LOG_LEVEL=INFO \ + -e SKIP_PROVIDER_TESTS=$(SKIP_PROVIDER_TESTS) \ + -e SKIP_RACE=$(SKIP_RACE) \ --rm \ - pactfoundation/pact-go-test \ + pactfoundation/pact-go-test-$(IMAGE_VARIANT) \ /bin/sh -c "make pact_local" docker_test_all: docker_build docker run \ -e LOG_LEVEL=INFO \ + -e SKIP_PROVIDER_TESTS=$(SKIP_PROVIDER_TESTS) \ + -e SKIP_RACE=$(SKIP_RACE) \ --rm \ - pactfoundation/pact-go-test \ + pactfoundation/pact-go-test-$(IMAGE_VARIANT) \ /bin/sh -c "make test && make pact_local" bin: @@ -114,7 +127,9 @@ pact: clean install docker pact_local: clean download_plugins install @echo "--- 🔨 Running Pact examples" go test -v -tags=consumer -count=1 github.com/pact-foundation/pact-go/v2/examples/... - SKIP_PUBLISH=true go test -v -timeout=30s -tags=provider -count=1 github.com/pact-foundation/pact-go/v2/examples/... + if [ "$(SKIP_PROVIDER_TESTS)" != "true" ]; then \ + SKIP_PUBLISH=true go test -v -timeout=30s -tags=provider -count=1 github.com/pact-foundation/pact-go/v2/examples/...; \ + fi publish: @echo "-- 📃 Publishing pacts" @@ -124,13 +139,19 @@ release: echo "--- 🚀 Releasing it" "$(CURDIR)/scripts/release.sh" +ifeq ($(SKIP_PROVIDER_TESTS),true) + PROVIDER_TEST_TAGS= +else + PROVIDER_TEST_TAGS=-tags=provider +endif + test: deps install @echo "--- ✅ Running tests" @if [ -f coverage.txt ]; then rm coverage.txt; fi; @echo "mode: count" > coverage.txt @for d in $$(go list ./... | grep -v vendor | grep -v examples); \ do \ - go test -v -race -coverprofile=profile.out -covermode=atomic $$d; \ + go test -v $(RACE) -coverprofile=profile.out $(PROVIDER_TEST_TAGS) -covermode=atomic $$d; \ if [ $$? != 0 ]; then \ exit 1; \ fi; \ @@ -143,7 +164,7 @@ test: deps install testrace: - go test -race $(TEST) $(TESTARGS) + go test $(RACE) $(TEST) $(TESTARGS) updatedeps: go get -d -v -p 2 ./... diff --git a/README.md b/README.md index 8cb16e7e0..3ab2ab1c3 100644 --- a/README.md +++ b/README.md @@ -285,12 +285,17 @@ _\*_ v3 support is limited to the subset of functionality required to enable lan | MacOS | arm64 | ✅ | All | | Linux (libc) | x86_64 | ✅ | All | | Linux (libc) | arm64 | ✅ | All | -| Linux (musl) | x86_64 | ❌ | - | -| Linux (musl) | arm64 | ❌ | - | +| Linux (musl) | x86_64 | ✅ | v2.2.0 | +| Linux (musl) | arm64 | ✅ | v2.2.0 | | Windows | x86_64 | ✅ | All | | Windows | x86 | ❌ | - | | Windows | arm64 | ❌ | - | +⚠️ - Known issues + +> - Linux (musl) provider verification is known to experience segmentation faults, reproducible in our CI system. +> - Assistance is greatly appreciated if musl support is important to you. + ## Roadmap diff --git a/consumer/http_v4_test.go b/consumer/http_v4_test.go index 6232bd794..33165496b 100644 --- a/consumer/http_v4_test.go +++ b/consumer/http_v4_test.go @@ -59,7 +59,7 @@ func TestHttpV4TypeSystem(t *testing.T) { UponReceiving("some scenario"). UsingPlugin(PluginConfig{ Plugin: "protobuf", - Version: "0.3.15", + Version: "0.5.4", }). WithRequest("GET", "/"). // WithRequest("GET", "/", func(b *V4InteractionWithPluginRequestBuilder) { diff --git a/examples/grpc/grpc_consumer_test.go b/examples/grpc/grpc_consumer_test.go index 06d0394f7..ba23e062c 100644 --- a/examples/grpc/grpc_consumer_test.go +++ b/examples/grpc/grpc_consumer_test.go @@ -53,7 +53,7 @@ func TestGetFeatureSuccess(t *testing.T) { Given("feature 'Big Tree' exists"). UsingPlugin(message.PluginConfig{ Plugin: "protobuf", - Version: "0.3.15", + Version: "0.5.4", }). WithContents(grpcInteraction, "application/protobuf"). StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional @@ -123,7 +123,7 @@ func TestGetFeatureError(t *testing.T) { Given("feature does not exist at -1, -1"). UsingPlugin(message.PluginConfig{ Plugin: "protobuf", - Version: "0.3.15", + Version: "0.5.4", }). WithContents(grpcInteraction, "application/protobuf"). StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional @@ -194,7 +194,7 @@ func TestSaveFeature(t *testing.T) { Given("feature does not exist at -1, -1"). UsingPlugin(message.PluginConfig{ Plugin: "protobuf", - Version: "0.3.15", + Version: "0.5.4", }). WithContents(grpcInteraction, "application/protobuf"). StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional diff --git a/examples/protobuf-message/protobuf_consumer_test.go b/examples/protobuf-message/protobuf_consumer_test.go index ca097fd6c..9fa0a61eb 100644 --- a/examples/protobuf-message/protobuf_consumer_test.go +++ b/examples/protobuf-message/protobuf_consumer_test.go @@ -43,7 +43,7 @@ func TestPluginMessageConsumer(t *testing.T) { ExpectsToReceive("feature message"). UsingPlugin(message.PluginConfig{ Plugin: "protobuf", - Version: "0.3.15", + Version: "0.5.4", }). WithContents(protoMessage, "application/protobuf"). ExecuteTest(t, func(m message.AsynchronousMessage) error { diff --git a/examples/protobuf-message/protobuf_provider_test.go b/examples/protobuf-message/protobuf_provider_test.go index bec3eb217..eb7d5ecb0 100644 --- a/examples/protobuf-message/protobuf_provider_test.go +++ b/examples/protobuf-message/protobuf_provider_test.go @@ -41,7 +41,7 @@ func TestPluginMessageProvider(t *testing.T) { }, }) return feature, message.Metadata{ - "contentType": "application/protobuf;message=Feature", // <- This is required to ensure the correct type is matched + "contentType": "application/protobuf;message=.routeguide.Feature", // <- This is required to ensure the correct type is matched }, nil }, } diff --git a/installer/installer.go b/installer/installer.go index dbc092d48..95c00c611 100644 --- a/installer/installer.go +++ b/installer/installer.go @@ -80,14 +80,6 @@ func NewInstaller(opts ...installerConfig) (*Installer, error) { log.Println("[WARN] amd64 architecture not detected, defaulting to x86_64. Behaviour may be undefined") } - // Only perform a check if current OS is linux - if runtime.GOOS == "linux" { - err := i.checkMusl() - if err != nil { - log.Println("[DEBUG] unable to check for presence musl library due to error:", err) - } - } - return i, nil } @@ -260,7 +252,13 @@ func (i *Installer) getDownloadURLForPackage(pkg string) (string, error) { return "", fmt.Errorf("unable to find package details for package: %s", pkg) } - return fmt.Sprintf(downloadTemplate, pkg, pkgInfo.version, osToLibName[i.os], i.os, i.arch, osToExtension[i.os]), nil + if checkMusl() && i.os == linux { + return fmt.Sprintf(downloadTemplate, pkg, pkgInfo.version, osToLibName[i.os], i.os, i.arch+"-musl", osToExtension[i.os]), nil + } else { + return fmt.Sprintf(downloadTemplate, pkg, pkgInfo.version, osToLibName[i.os], i.os, i.arch, osToExtension[i.os]), nil + + } + } func (i *Installer) getLibDstForPackage(pkg string) (string, error) { @@ -331,20 +329,23 @@ func checkVersion(lib, version, versionRange string) error { } // checkMusl checks if the OS uses musl library instead of glibc -func (i *Installer) checkMusl() error { +func checkMusl() bool { lddPath, err := exec.LookPath("ldd") if err != nil { - return fmt.Errorf("could not find ldd in environment path") + return false } cmd := exec.Command(lddPath, "/bin/echo") out, err := cmd.CombinedOutput() + if err != nil { + return false + } if strings.Contains(string(out), "musl") { - log.Println("[WARN] Usage of musl library is known to cause problems, prefer using glibc instead.") + return true } - return err + return false } // download template structure: "https://github.com/pact-foundation/pact-reference/releases/download/PACKAGE-vVERSION/LIBNAME-OS-ARCH.EXTENSION.gz" diff --git a/installer/installer_test.go b/installer/installer_test.go index b492af492..dd6c85492 100644 --- a/installer/installer_test.go +++ b/installer/installer_test.go @@ -35,16 +35,37 @@ func TestInstallerDownloader(t *testing.T) { test Installer }{ { - name: "ffi lib - linux x86", + name: "ffi lib - linux x86_64", pkg: FFIPackage, - want: fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-linux-x86_64.so.gz", packages[FFIPackage].version), + want: func() string { + if checkMusl() { + return fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-linux-x86_64-musl.so.gz", packages[FFIPackage].version) + } else { + return fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-linux-x86_64.so.gz", packages[FFIPackage].version) + } + }(), test: Installer{ os: linux, arch: x86_64, }, }, { - name: "ffi lib - macos x86", + name: "ffi lib - linux aarch64", + pkg: FFIPackage, + want: func() string { + if checkMusl() { + return fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-linux-aarch64-musl.so.gz", packages[FFIPackage].version) + } else { + return fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-linux-aarch64.so.gz", packages[FFIPackage].version) + } + }(), + test: Installer{ + os: linux, + arch: aarch64, + }, + }, + { + name: "ffi lib - macos x86_64", pkg: FFIPackage, want: fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-macos-x86_64.dylib.gz", packages[FFIPackage].version), test: Installer{ @@ -62,7 +83,7 @@ func TestInstallerDownloader(t *testing.T) { }, }, { - name: "ffi lib - windows x86", + name: "ffi lib - windows x86_64", pkg: FFIPackage, want: fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/pact_ffi-windows-x86_64.dll.gz", packages[FFIPackage].version), test: Installer{ diff --git a/internal/native/message_server_test.go b/internal/native/message_server_test.go index c24df1edc..0431b8e05 100644 --- a/internal/native/message_server_test.go +++ b/internal/native/message_server_test.go @@ -213,7 +213,7 @@ func TestGetPluginSyncMessageContentsAsBytes(t *testing.T) { // Protobuf plugin test defer m.CleanupPlugins() - err := m.UsingPlugin("protobuf", "0.3.15") + err := m.UsingPlugin("protobuf", "0.5.4") assert.NoError(t, err) i := m.NewSyncMessageInteraction("grpc interaction") @@ -271,7 +271,7 @@ func TestGetPluginSyncMessageContentsAsBytes_EmptyResponse(t *testing.T) { // Protobuf plugin test defer m.CleanupPlugins() - err := m.UsingPlugin("protobuf", "0.3.15") + err := m.UsingPlugin("protobuf", "0.5.4") assert.NoError(t, err) i := m.NewSyncMessageInteraction("grpc interaction") @@ -318,7 +318,7 @@ func TestGetPluginAsyncMessageContentsAsBytes(t *testing.T) { // Protobuf plugin test defer m.CleanupPlugins() - _ = m.UsingPlugin("protobuf", "0.3.15") + _ = m.UsingPlugin("protobuf", "0.5.4") i := m.NewAsyncMessageInteraction("grpc interaction") @@ -359,7 +359,7 @@ func TestGrpcPluginInteraction(t *testing.T) { // Protobuf plugin test defer m.CleanupPlugins() - _ = m.UsingPlugin("protobuf", "0.3.15") + _ = m.UsingPlugin("protobuf", "0.5.4") i := m.NewSyncMessageInteraction("grpc interaction") @@ -437,7 +437,7 @@ func TestGrpcPluginInteraction_ErrorResponse(t *testing.T) { // Protobuf plugin test defer m.CleanupPlugins() - _ = m.UsingPlugin("protobuf", "0.3.15") + _ = m.UsingPlugin("protobuf", "0.5.4") i := m.NewSyncMessageInteraction("grpc interaction") diff --git a/internal/native/mock_server_test.go b/internal/native/mock_server_test.go index 2e991aa6c..aa79a6373 100644 --- a/internal/native/mock_server_test.go +++ b/internal/native/mock_server_test.go @@ -174,7 +174,7 @@ func TestPluginInteraction(t *testing.T) { // Protobuf plugin test defer m.CleanupPlugins() - _ = m.UsingPlugin("protobuf", "0.3.15") + _ = m.UsingPlugin("protobuf", "0.5.4") m.WithSpecificationVersion(SPECIFICATION_VERSION_V4) i := m.NewInteraction("some plugin interaction") diff --git a/internal/native/verifier_test.go b/internal/native/verifier_test.go index 6105bc93a..714d65551 100644 --- a/internal/native/verifier_test.go +++ b/internal/native/verifier_test.go @@ -1,3 +1,6 @@ +//go:build provider +// +build provider + package native import ( diff --git a/message/v4/synchronous_message_test.go b/message/v4/synchronous_message_test.go index 0c5dd147f..3b1d4ac24 100644 --- a/message/v4/synchronous_message_test.go +++ b/message/v4/synchronous_message_test.go @@ -111,7 +111,7 @@ func TestSyncTypeSystem_ProtobufPlugin_Matcher_Transport(t *testing.T) { Given("some state"). UsingPlugin(PluginConfig{ Plugin: "protobuf", - Version: "0.3.15", + Version: "0.5.4", }). WithContents(grpcInteraction, "application/protobuf"). StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional @@ -168,7 +168,7 @@ func TestSyncTypeSystem_ProtobufPlugin_Matcher_Transport_Fail(t *testing.T) { Given("some state"). UsingPlugin(PluginConfig{ Plugin: "protobuf", - Version: "0.3.15", + Version: "0.5.4", }). WithContents(grpcInteraction, "application/protobuf"). StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional diff --git a/scripts/install-cli.sh b/scripts/install-cli.sh index b4151f940..66817bd0c 100755 --- a/scripts/install-cli.sh +++ b/scripts/install-cli.sh @@ -38,7 +38,7 @@ function detect_osarch() { } -VERSION="0.1.2" +VERSION="0.1.3" detect_osarch if [ ! -f ~/.pact/bin/pact-plugin-cli ]; then