From 57466dd68e81742ab57c53eac6cbea4e9dac26fa Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 20 Oct 2023 22:15:47 +0100 Subject: [PATCH] feat: add golangci-lint (local) version check (#467) * feat: add golangci-lint version check Add golangci-lint version check to compare the local version against the workflow version. Adds associated test workflow for mac, ubuntu and windows. Signed-off-by: mikeee * fix: add install linter step Signed-off-by: mikeee * fix: return the raw output of the command run Signed-off-by: mikeee * fix: change regex to pick up versions not prefixed Signed-off-by: mikeee * formatting: gofumpt'ed workspace Signed-off-by: mikeee * fix: convert line endings to LF on checkout Signed-off-by: mikeee * fix: encapsulate any spaces in the argument Signed-off-by: mikeee --------- Signed-off-by: mikeee --- .gitattributes | 1 + .github/workflows/test-tooling.yml | 59 +++++++++++++ Makefile | 8 +- actor/mock/mock_server.go | 14 +-- tools/check-lint-version/Makefile | 22 +++++ tools/check-lint-version/Readme.md | 12 +++ tools/check-lint-version/go.mod | 16 ++++ tools/check-lint-version/go.sum | 24 +++++ tools/check-lint-version/main.go | 80 +++++++++++++++++ tools/check-lint-version/main_test.go | 88 +++++++++++++++++++ .../testing/invalid-test.yml | 12 +++ .../testing/invalid-yaml.yml | 1 + 12 files changed, 329 insertions(+), 8 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/test-tooling.yml create mode 100644 tools/check-lint-version/Makefile create mode 100644 tools/check-lint-version/Readme.md create mode 100644 tools/check-lint-version/go.mod create mode 100644 tools/check-lint-version/go.sum create mode 100644 tools/check-lint-version/main.go create mode 100644 tools/check-lint-version/main_test.go create mode 100644 tools/check-lint-version/testing/invalid-test.yml create mode 100644 tools/check-lint-version/testing/invalid-yaml.yml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a0717e4b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.go text eol=lf \ No newline at end of file diff --git a/.github/workflows/test-tooling.yml b/.github/workflows/test-tooling.yml new file mode 100644 index 00000000..8389de7e --- /dev/null +++ b/.github/workflows/test-tooling.yml @@ -0,0 +1,59 @@ +name: Test Tooling + +on: + push: + paths: # Explicitly declare which paths + - ".github/workflows/test-tooling.yml" + - "tools/*" + pull_request: + branches: + - main + paths: # Explicitly declare which paths + - ".github/workflows/test-tooling.yml" + - "tools/*" + +jobs: + build: + name: Test (${{ matrix.os}}) go ${{ matrix.gover }} + + strategy: + fail-fast: false + matrix: + gover: + - "1.21" + os: + - "ubuntu-latest" + - "windows-latest" + - "macos-latest" + runs-on: ${{ matrix.os }} + env: + GOVER: ${{ matrix.gover }} + GOLANGCILINT_VER: v1.54.2 # Make sure to bump /tools/check-lint-version/main_test.go + + steps: + - name: Setup + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GOVER }} + + - name: Checkout + uses: actions/checkout@v4 + + - name: Tidy + working-directory: ./tools/check-lint-version + run: make tidy + + - name: Lint + uses: golangci/golangci-lint-action@v3 + with: + version: ${{ env.GOLANGCILINT_VER }} + working-directory: ./tools/check-lint-version + skip-cache: true + args: --timeout=10m0s --config ../../.golangci.yml + + - name: Install Linter + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)/bin" ${{ env.GOLANGCILINT_VER }} + + - name: Test + working-directory: ./tools/check-lint-version + run: make test \ No newline at end of file diff --git a/Makefile b/Makefile index 01ea3552..58013335 100644 --- a/Makefile +++ b/Makefile @@ -29,9 +29,15 @@ cover: ## Displays test coverage in the client and service packages go test -coverprofile=cover-http.out ./service/http && go tool cover -html=cover-http.out .PHONY: lint -lint: ## Lints the entire project +lint: check-lint ## Lints the entire project golangci-lint run --timeout=3m +.PHONY: check-lint +check-lint: ## Compares the locally installed linter with the workflow version + cd ./tools/check-lint-version && \ + go mod tidy && \ + go run main.go + .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) diff --git a/actor/mock/mock_server.go b/actor/mock/mock_server.go index 887aa96c..882dca9e 100644 --- a/actor/mock/mock_server.go +++ b/actor/mock/mock_server.go @@ -256,16 +256,16 @@ func (mr *MockServerContextMockRecorder) Type() *gomock.Call { } func (mr *MockServerContextMockRecorder) Invoke(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Invoke", reflect.TypeOf((*MockServerContext)(nil).Invoke), arg0, arg1) + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Invoke", reflect.TypeOf((*MockServerContext)(nil).Invoke), arg0, arg1) } func (m *MockServerContext) Invoke(ctx context.Context, input string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Invoke", ctx, input) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Invoke", ctx, input) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 } // MockReminderCallee is a mock of ReminderCallee interface. diff --git a/tools/check-lint-version/Makefile b/tools/check-lint-version/Makefile new file mode 100644 index 00000000..a5698f2c --- /dev/null +++ b/tools/check-lint-version/Makefile @@ -0,0 +1,22 @@ +.PHONY: cover +cover: + go test -coverprofile=cover.out ./ && go tool cover -html=cover.out + +.PHONY: tidy +tidy: ## Updates the go modules + go mod tidy + +.PHONY: test +test: + go test -count=1 \ + -race \ + -coverprofile=coverage.txt \ + -covermode=atomic \ + ./... + +.PHONY: lint +lint: check-lint-version + golangci-lint run --timeout=3m --config ../../.golangci.yml + +check-lint-version: tidy + go run main.go \ No newline at end of file diff --git a/tools/check-lint-version/Readme.md b/tools/check-lint-version/Readme.md new file mode 100644 index 00000000..d73ed184 --- /dev/null +++ b/tools/check-lint-version/Readme.md @@ -0,0 +1,12 @@ +# Check Lint Version + +This package is designed to check the local golangci-lint version against that of the current github workflow. + +## Usage + +In the repo root, you can use the `make lint` command which makes use of this to verify the golangci-lint version and +run the linter. + +## Workflow + +The `test-tooling` workflow is responsible for testing this package. \ No newline at end of file diff --git a/tools/check-lint-version/go.mod b/tools/check-lint-version/go.mod new file mode 100644 index 00000000..43d97119 --- /dev/null +++ b/tools/check-lint-version/go.mod @@ -0,0 +1,16 @@ +module github.com/dapr/go-sdk/tools/check-lint-version + +go 1.19 + +require ( + github.com/stretchr/testify v1.8.4 + golang.org/x/mod v0.13.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect +) diff --git a/tools/check-lint-version/go.sum b/tools/check-lint-version/go.sum new file mode 100644 index 00000000..b06a6d4a --- /dev/null +++ b/tools/check-lint-version/go.sum @@ -0,0 +1,24 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/check-lint-version/main.go b/tools/check-lint-version/main.go new file mode 100644 index 00000000..fcdf75fe --- /dev/null +++ b/tools/check-lint-version/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "regexp" + + "golang.org/x/mod/semver" + "gopkg.in/yaml.v3" +) + +type GHWorkflow struct { + Jobs struct { + Build struct { + Env struct { + GOVER string `yaml:"GOVER"` + GOLANGCILINTVER string `yaml:"GOLANGCILINT_VER"` + } `yaml:"env"` + } `yaml:"build"` + } `yaml:"jobs"` +} + +func parseWorkflowVersionFromFile(path string) (string, error) { + var ghWorkflow GHWorkflow + + raw, err := os.ReadFile(path) + if err != nil { + return "", err + } + err = yaml.Unmarshal(raw, &ghWorkflow) + if err != nil { + return "", err + } + return ghWorkflow.Jobs.Build.Env.GOLANGCILINTVER, err +} + +func getCurrentVersion() (string, error) { + out, err := exec.Command("golangci-lint", "--version").Output() + if err != nil { + return "", err + } + + regex, err := regexp.Compile(`golangci-lint\shas\sversion\sv?([\d+.]+[\d])`) + if err != nil { + return "", err + } + + matches := regex.FindStringSubmatch(string(out)) + + if matches == nil { + return "", fmt.Errorf("no version found: %v", string(out)) + } + return fmt.Sprintf("v%s", matches[1]), err +} + +func isVersionValid(workflowVersion, currentVersion string) bool { + res := semver.MajorMinor(workflowVersion) == semver.MajorMinor(currentVersion) + return res +} + +func compareVersions(path string) string { + workflowVersion, err := parseWorkflowVersionFromFile(path) + if err != nil { + return fmt.Sprintf("Error parsing workflow version: %v", err) + } + currentVersion, err := getCurrentVersion() + if err != nil { + return fmt.Sprintf("Error getting current version: %v", err) + } + validVersion := isVersionValid(workflowVersion, currentVersion) + if !validVersion { + return fmt.Sprintf("Invalid version, expected: %s, current: %s - See: https://golangci-lint.run/usage/install/ for instructions to update", workflowVersion, currentVersion) + } + return fmt.Sprintf("Linter version is valid (MajorMinor): %s", currentVersion) +} + +func main() { + fmt.Println(compareVersions("../../.github/workflows/test-on-push.yaml")) +} diff --git a/tools/check-lint-version/main_test.go b/tools/check-lint-version/main_test.go new file mode 100644 index 00000000..e7c842d9 --- /dev/null +++ b/tools/check-lint-version/main_test.go @@ -0,0 +1,88 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseWorkflow(t *testing.T) { + t.Run("parse invalid workflow file", func(t *testing.T) { + parsedVersion, err := parseWorkflowVersionFromFile("../../.github/workflows/invalid.yaml") + assert.Equal(t, "", parsedVersion) + assert.Error(t, err) + }) + + t.Run("parse workflow file with a missing key", func(t *testing.T) { + parsedVersion, err := parseWorkflowVersionFromFile("./testing/invalid-test.yml") + assert.Equal(t, "", parsedVersion) + assert.NoError(t, err) + }) + + t.Run("parse an invalid workflow file", func(t *testing.T) { + parsedVersion, err := parseWorkflowVersionFromFile("./testing/invalid-yaml.yml") + assert.Equal(t, "", parsedVersion) + assert.Error(t, err) + }) + + t.Run("parse testing workflow file", func(t *testing.T) { + parsedVersion, err := parseWorkflowVersionFromFile("../../.github/workflows/test-tooling.yml") + assert.Equal(t, "v1.54.2", parsedVersion) + assert.NoError(t, err) + }) +} + +func TestGetCurrentVersion(t *testing.T) { + t.Run("get current version from system", func(t *testing.T) { + currentVersion, err := getCurrentVersion() + assert.Equal(t, "v1.54.2", currentVersion) + assert.NoError(t, err) + }) + + // TODO: test failure to detect current version + + // TODO: test failure to compile regex expression + + // TODO: test failure finding matches +} + +func TestIsVersionValid(t *testing.T) { + t.Run("compare versions - exactly equal to", func(t *testing.T) { + assert.Equal(t, true, isVersionValid("v1.54.2", "v1.54.2")) + }) + + t.Run("compare versions - patch version greater (workflow)", func(t *testing.T) { + assert.Equal(t, true, isVersionValid("v1.54.3", "v1.54.2")) + }) + + t.Run("compare versions - patch version greater (installed)", func(t *testing.T) { + assert.Equal(t, true, isVersionValid("v1.54.2", "v1.54.3")) + }) + + t.Run("compare versions - invalid (installed)", func(t *testing.T) { + assert.Equal(t, false, isVersionValid("v1.54.2", "v1.52.2")) + }) + + t.Run("compare versions - invalid (workflow)", func(t *testing.T) { + assert.Equal(t, false, isVersionValid("v1.52.2", "v1.54.2")) + }) +} + +func TestCompareVersions(t *testing.T) { + t.Run("Valid comparison", func(t *testing.T) { + res := compareVersions("../../.github/workflows/test-on-push.yaml") + assert.Contains(t, res, "Linter version is valid") + }) + + t.Run("Invalid comparison", func(t *testing.T) { + res := compareVersions("./testing/invalid-test.yml") + assert.Contains(t, res, "Invalid version") + }) + + // TODO: test function for failure to get the current version using getCurrentVersion() + + t.Run("Invalid path for comparison", func(t *testing.T) { + res := compareVersions("./testing/invalid-test-incorrect-path.yml") + assert.Contains(t, res, "Error parsing workflow") + }) +} diff --git a/tools/check-lint-version/testing/invalid-test.yml b/tools/check-lint-version/testing/invalid-test.yml new file mode 100644 index 00000000..346ba3a3 --- /dev/null +++ b/tools/check-lint-version/testing/invalid-test.yml @@ -0,0 +1,12 @@ +name: Test + +on: + push: + pull_request: + branches: + - main + +jobs: + build: + env: + NOGOLANGCILINT_VER: "123.123.123" \ No newline at end of file diff --git a/tools/check-lint-version/testing/invalid-yaml.yml b/tools/check-lint-version/testing/invalid-yaml.yml new file mode 100644 index 00000000..e7cbb71a --- /dev/null +++ b/tools/check-lint-version/testing/invalid-yaml.yml @@ -0,0 +1 @@ +testfile \ No newline at end of file