diff --git a/.github/workflows/cloc.yml b/.github/workflows/cloc.yml index 4592bb4..e0c3825 100755 --- a/.github/workflows/cloc.yml +++ b/.github/workflows/cloc.yml @@ -13,24 +13,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: path: pr - name: Checkout base code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.base.sha }} path: base - - name: Count Lines Of Code + - name: Count lines of code id: loc run: | - curl -sLO https://github.com/vearutop/sccdiff/releases/download/v1.0.2/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz && echo "b17e76bede22af0206b4918d3b3c4e7357f2a21b57f8de9e7c9dc0eb56b676c0 sccdiff" | shasum -c + curl -sLO https://github.com/vearutop/sccdiff/releases/download/v1.0.3/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz + sccdiff_hash=$(git hash-object ./sccdiff) + [ "$sccdiff_hash" == "ae8a07b687bd3dba60861584efe724351aa7ff63" ] || (echo "::error::unexpected hash for sccdiff, possible tampering: $sccdiff_hash" && exit 1) OUTPUT=$(cd pr && ../sccdiff -basedir ../base) echo "${OUTPUT}" - OUTPUT="${OUTPUT//$'\n'/%0A}" - echo "::set-output name=diff::$OUTPUT" + echo "diff<> $GITHUB_OUTPUT && echo "$OUTPUT" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT - - name: Comment Code Lines + - name: Comment lines of code continue-on-error: true uses: marocchino/sticky-pull-request-comment@v2 with: diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 48207f9..f435fe8 100755 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -19,15 +19,15 @@ jobs: name: golangci-lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 with: - go-version: 1.18.x - - uses: actions/checkout@v2 + go-version: stable + - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v3.1.0 + uses: golangci/golangci-lint-action@v8.0.0 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.46.2 + version: v2.4.0 # Optional: working directory, useful for monorepos # working-directory: somedir diff --git a/.github/workflows/gorelease.yml b/.github/workflows/gorelease.yml index f1c678a..531f975 100755 --- a/.github/workflows/gorelease.yml +++ b/.github/workflows/gorelease.yml @@ -9,29 +9,19 @@ concurrency: cancel-in-progress: true env: - GO_VERSION: 1.18.x + GO_VERSION: stable jobs: gorelease: runs-on: ubuntu-latest steps: - - name: Install Go stable - if: env.GO_VERSION != 'tip' - uses: actions/setup-go@v3 + - name: Install Go + uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - - name: Install Go tip - if: env.GO_VERSION == 'tip' - run: | - curl -sL https://storage.googleapis.com/go-build-snap/go/linux-amd64/$(git ls-remote https://github.com/golang/go.git HEAD | awk '{print $1;}').tar.gz -o gotip.tar.gz - ls -lah gotip.tar.gz - mkdir -p ~/sdk/gotip - tar -C ~/sdk/gotip -xzf gotip.tar.gz - ~/sdk/gotip/bin/go version - echo "PATH=$HOME/go/bin:$HOME/sdk/gotip/bin/:$PATH" >> $GITHUB_ENV - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Gorelease cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/go/bin/gorelease @@ -42,9 +32,8 @@ jobs: test -e ~/go/bin/gorelease || go install golang.org/x/exp/cmd/gorelease@latest OUTPUT=$(gorelease 2>&1 || exit 0) echo "${OUTPUT}" - OUTPUT="${OUTPUT//$'\n'/%0A}" - echo "::set-output name=report::$OUTPUT" - - name: Comment Report + echo "report<> $GITHUB_OUTPUT && echo "$OUTPUT" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT + - name: Comment report continue-on-error: true uses: marocchino/sticky-pull-request-comment@v2 with: diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 60cdcda..1593f19 100755 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -15,36 +15,25 @@ concurrency: env: GO111MODULE: "on" RUN_BASE_COVERAGE: "on" # Runs test for PR base in case base test coverage is missing. - COV_GO_VERSION: 1.18.x # Version of Go to collect coverage + COV_GO_VERSION: stable # Version of Go to collect coverage TARGET_DELTA_COV: 90 # Target coverage of changed lines, in percents jobs: test: strategy: matrix: - go-version: [ 1.16.x, 1.17.x, 1.18.x ] + go-version: [ 1.16.x, stable, oldstable ] runs-on: ubuntu-latest steps: - - name: Install Go stable - if: matrix.go-version != 'tip' - uses: actions/setup-go@v3 + - name: Install Go + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - - name: Install Go tip - if: matrix.go-version == 'tip' - run: | - curl -sL https://storage.googleapis.com/go-build-snap/go/linux-amd64/$(git ls-remote https://github.com/golang/go.git HEAD | awk '{print $1;}').tar.gz -o gotip.tar.gz - ls -lah gotip.tar.gz - mkdir -p ~/sdk/gotip - tar -C ~/sdk/gotip -xzf gotip.tar.gz - ~/sdk/gotip/bin/go version - echo "PATH=$HOME/go/bin:$HOME/sdk/gotip/bin/:$PATH" >> $GITHUB_ENV - - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Go cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: # In order: # * Module download cache @@ -59,7 +48,7 @@ jobs: - name: Restore base test coverage id: base-coverage if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != '' - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | unit-base.txt @@ -82,26 +71,27 @@ jobs: go tool cover -func=./unit.coverprofile > unit.txt TOTAL=$(grep 'total:' unit.txt) echo "${TOTAL}" - echo "::set-output name=total::$TOTAL" + echo "total=$TOTAL" >> $GITHUB_OUTPUT - name: Annotate missing test coverage id: annotate if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != '' run: | - git fetch origin master ${{ github.event.pull_request.base.sha }} - curl -sLO https://github.com/vearutop/gocovdiff/releases/download/v1.3.4/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz && shasum -a 256 gocovdiff && echo "b351c67526eefeb0671c82e9271ae984875865eed19e911f40f78348cb98347c gocovdiff" | shasum -c - REP=$(./gocovdiff -cov unit.coverprofile -gha-annotations gha-unit.txt -delta-cov-file delta-cov-unit.txt -target-delta-cov ${TARGET_DELTA_COV}) + curl -sLO https://github.com/vearutop/gocovdiff/releases/download/v1.4.2/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz && rm linux_amd64.tar.gz + gocovdiff_hash=$(git hash-object ./gocovdiff) + [ "$gocovdiff_hash" == "c37862c73a677e5a9c069470287823ab5bbf0244" ] || (echo "::error::unexpected hash for gocovdiff, possible tampering: $gocovdiff_hash" && exit 1) + # Fetch PR diff from GitHub API. + curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3.diff" https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} > pull_request.diff + REP=$(./gocovdiff -diff pull_request.diff -mod github.com/$GITHUB_REPOSITORY -cov unit.coverprofile -gha-annotations gha-unit.txt -delta-cov-file delta-cov-unit.txt -target-delta-cov ${TARGET_DELTA_COV}) echo "${REP}" - REP="${REP//$'\n'/%0A}" cat gha-unit.txt - DIFF=$(test -e unit-base.txt && ./gocovdiff -func-cov unit.txt -func-base-cov unit-base.txt || echo "Missing base coverage file") - DIFF="${DIFF//$'\n'/%0A}" + DIFF=$(test -e unit-base.txt && ./gocovdiff -mod github.com/$GITHUB_REPOSITORY -func-cov unit.txt -func-base-cov unit-base.txt || echo "Missing base coverage file") TOTAL=$(cat delta-cov-unit.txt) - echo "::set-output name=rep::$REP" - echo "::set-output name=diff::$DIFF" - echo "::set-output name=total::$TOTAL" + echo "rep<> $GITHUB_OUTPUT && echo "$REP" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT + echo "diff<> $GITHUB_OUTPUT && echo "$DIFF" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT + echo "total<> $GITHUB_OUTPUT && echo "$TOTAL" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT - - name: Comment Test Coverage + - name: Comment test coverage continue-on-error: true if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != '' uses: marocchino/sticky-pull-request-comment@v2 @@ -130,7 +120,7 @@ jobs: - name: Upload code coverage if: matrix.go-version == env.COV_GO_VERSION - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v5 with: - file: ./unit.coverprofile + files: ./unit.coverprofile flags: unittests diff --git a/.golangci.yml b/.golangci.yml index 9fa56cf..a399ba1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,54 +1,79 @@ -# See https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml +# See https://golangci-lint.run/docs/linters/configuration/ +version: "2" run: tests: true - -linters-settings: - errcheck: - check-type-assertions: true - check-blank: true - gocyclo: - min-complexity: 20 - dupl: - threshold: 100 - misspell: - locale: US - unused: - check-exported: false - unparam: - check-exported: true - linters: - enable-all: true + default: all disable: - - lll - - maligned - - gochecknoglobals - - gomnd - - wrapcheck - - paralleltest + - noinlineerr + - wsl_v5 + - funcorder + - copyloopvar + - depguard + - dupword + - errname + - exhaustruct - forbidigo - - exhaustivestruct - - interfacer # deprecated - forcetypeassert - - scopelint # deprecated - - ifshort # too many false positives - - golint # deprecated - - varnamelen - - tagliatelle - - errname + - gochecknoglobals + - intrange - ireturn - - exhaustruct + - lll + - mnd - nonamedreturns - -issues: - exclude-use-default: false - exclude-rules: - - linters: - - gomnd - - goconst - - goerr113 - - noctx - - funlen - - dupl - path: "_test.go" - + - paralleltest + - recvcheck + - tagalign + - tagliatelle + - testableexamples + - testifylint + - varnamelen + - wrapcheck + settings: + dupl: + threshold: 100 + errcheck: + check-type-assertions: true + check-blank: true + gocyclo: + min-complexity: 20 + misspell: + locale: US + unparam: + check-exported: true + exclusions: + generated: lax + rules: + - linters: + - gosec + - dupl + - funlen + - goconst + - mnd + - noctx + - unparam + - unused + path: _test.go + - linters: + - errcheck + - gosec + path: example_ + - linters: + - revive + text: 'unused-parameter: parameter' + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index c181688..2673221 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -#GOLANGCI_LINT_VERSION := "v1.46.2" # Optional configuration to pinpoint golangci-lint version. +#GOLANGCI_LINT_VERSION := "v2.5.0" # Optional configuration to pinpoint golangci-lint version. # The head of Makefile determines location of dev-go to include standard targets. GO ?= go diff --git a/go.mod b/go.mod index 41487a3..8f62eb7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/bool64/shared go 1.17 require ( - github.com/bool64/dev v0.2.17 + github.com/bool64/dev v0.2.43 github.com/stretchr/testify v1.4.0 ) diff --git a/go.sum b/go.sum index 7767614..1c9bf86 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/bool64/dev v0.2.17 h1:jE+T92oazAIV8fvMDJrKjsF1bzfr5XezZ8bM5GS1Cl0= -github.com/bool64/dev v0.2.17/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/dev v0.2.43 h1:yQ7qiZVef6WtCl2vDYU0Y+qSq+0aBrQzY8KXkklk9cQ= +github.com/bool64/dev v0.2.43/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/vars.go b/vars.go index 137bf5c..1c2065d 100644 --- a/vars.go +++ b/vars.go @@ -3,6 +3,9 @@ package shared import ( "context" + "encoding/json" + "math" + "strconv" "strings" "sync" ) @@ -99,6 +102,8 @@ func (v *Vars) Set(key string, val interface{}) { v.vars = make(map[string]interface{}) } + val = decodeJSONNumbers(val) + v.vars[key] = val for _, f := range v.onSet { @@ -162,3 +167,44 @@ func VarsFromContext(ctx context.Context) map[string]interface{} { return m } + +func decodeJSONNumbers(v interface{}) interface{} { + switch vv := v.(type) { + case json.Number: + return DecodeJSONNumber(vv) + case []interface{}: + for i, e := range vv { + vv[i] = decodeJSONNumbers(e) + } + + return vv + case map[string]interface{}: + for k, e := range vv { + vv[k] = decodeJSONNumbers(e) + } + + return vv + } + + return v +} + +// DecodeJSONNumber tries to decode a json.Number into the most appropriate type (unit64, int64, float64). +// If nothing works, json.Number is returned as is. +func DecodeJSONNumber(n json.Number) interface{} { + if strings.Contains(n.String(), ".") { + if f, err := n.Float64(); err == nil { + return f + } + } else if u, err := strconv.ParseUint(n.String(), 10, 64); err == nil { + if u <= uint64(math.MaxInt64) { + return int64(u) + } + + return u + } else if i, err := n.Int64(); err == nil { + return i + } + + return n +} diff --git a/vars_test.go b/vars_test.go index 1ad6320..b7061c0 100644 --- a/vars_test.go +++ b/vars_test.go @@ -2,6 +2,7 @@ package shared_test import ( "context" + "encoding/json" "sync" "testing" @@ -88,3 +89,31 @@ func TestVarToContext(t *testing.T) { ctx = shared.VarToContext(pCtx, "$quux", true) assert.Equal(t, map[string]interface{}{"$foo": "bar", "$baz": "qux", "$quux": true}, shared.VarsFromContext(ctx)) } + +func TestDecodeJSONNumber(t *testing.T) { + assert.Equal(t, int64(1), shared.DecodeJSONNumber("1")) + assert.Equal(t, int64(-1), shared.DecodeJSONNumber("-1")) + assert.Equal(t, 1.0, shared.DecodeJSONNumber("1.0")) + assert.Equal(t, 1.23, shared.DecodeJSONNumber("1.23")) + assert.Equal(t, uint64(17294094973108486143), shared.DecodeJSONNumber("17294094973108486143")) +} + +func TestVars_Set_jsonNumber(t *testing.T) { + v := shared.Vars{} + + val := map[string]interface{}{ + "foo": json.Number("123"), + "bar": json.Number("123.456"), + "baz": []interface{}{json.Number("123"), json.Number("123.456"), 789}, + } + + v.Set("test", val) + + val2, ok := v.Get("test") + assert.True(t, ok) + assert.Equal(t, map[string]interface{}{ + "foo": int64(123), + "bar": 123.456, + "baz": []interface{}{int64(123), 123.456, 789}, + }, val2) +}