diff --git a/.gitignore b/.gitignore index 4b40cb8..7f0d404 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ scripts/ .idea codeowners-validator -dist/ \ No newline at end of file +dist/ +tmp/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index f1fb593..c81be5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,6 @@ language: go -go: - - 1.12.x - - 1.13.x - -# Skip the install step. Don't `go get` dependencies. Only build with the code in vendor/ +# Skip the install step. Don't `go get` dependencies install: skip branches: @@ -15,16 +11,44 @@ env: - GO111MODULE=on - RUN_ON_CI=true +script: skip + jobs: include: - stage: test - name: "Before commit checks" - before_script: - # Download the binary to bin folder in $GOPATH - - curl -L -s https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 -o $GOPATH/bin/dep - # Make the binary executable - - chmod +x $GOPATH/bin/dep - script: ./hack/ci/run-tests.sh + name: "Build and unit-test with Go 1.12.x" + go: 1.12.x + script: make test-unit + - stage: test + name: "Build and unit-test with Go 1.13.x" + go: 1.13.x + script: make test-unit + - stage: test + name: "Hammer unit-test with Go 1.13.x" + go: 1.13.x + script: make test-hammer - stage: test name: "Code Quality Analysis" - script: ./hack/ci/run-lint.sh + script: make test-lint + go: 1.13.x + - stage: test-integration + name: "Integration testing on OSX" + script: make test-integration + os: osx + go: 1.13.x + - stage: test-integration + name: "Integration testing on Linux" + script: make test-integration + os: linux + go: 1.13.x +# Currently skipped as the new line encoding differs between linux and windows +# - stage: test-integration +# name: "Integration testing on Windows" +# before_install: +# - export "BINARY_PATH=$TRAVIS_BUILD_DIR/codeowners-validator.exe" +# script: +# - go build -o codeowners-validator.exe ./main.go +# - echo $(pwd) +# - go test ./tests/integration/... -v -tags=integration +# os: windows +# go: 1.13.x diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..df3af13 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +.DEFAULT_GOAL = build + +# enable module support across all go commands. +export GO111MODULE = on +# enable consistent Go 1.12/1.13 GOPROXY behavior. +export GOPROXY = https://proxy.golang.org + +# Build + +build: + go build -o codeowners-validator ./main.go +.PHONY: build + +build-race: + go build -race -o codeowners-validator ./main.go +.PHONY: build-race + +# Test +test-unit: + ./hack/ci/run-test-unit.sh +.PHONY: test-unit + +test-integration: build + env BINARY_PATH=$(PWD)/codeowners-validator ./hack/ci/run-test-integration.sh +.PHONY: test-integration + +test-lint: + ./hack/ci/run-lint.sh +.PHONY: test-lint + +test-hammer: + go test -count=100 ./... +.PHONY: test-hammer + +cover-html: + go test -v -coverprofile=./tmp/coverage.out ./... + go tool cover -html=./tmp/coverage.out +.PHONY: cover-html diff --git a/go.mod b/go.mod index 3f7b995..982b762 100644 --- a/go.mod +++ b/go.mod @@ -6,15 +6,21 @@ require ( github.com/fatih/color v1.9.0 github.com/google/go-github/v29 v29.0.3 github.com/hashicorp/go-multierror v1.0.0 + github.com/kr/text v0.2.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pkg/errors v0.9.1 + github.com/sergi/go-diff v1.1.0 // indirect github.com/sirupsen/logrus v1.4.2 github.com/spf13/afero v1.2.2 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.5.1 github.com/vrischmann/envconfig v1.2.0 - golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 // indirect + golang.org/x/crypto v0.0.0-20200320181102-891825fb96df // indirect + golang.org/x/net v0.0.0-20200320220750-118fecf932d8 // indirect golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 + golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544 + gopkg.in/src-d/go-git.v4 v4.13.1 + gotest.tools v2.2.0+incompatible ) diff --git a/go.sum b/go.sum index 928d213..8c4c641 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,28 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-github/v29 v29.0.3 h1:IktKCTwU//aFHnpA+2SLIi7Oo9uhAzgsdZNbcAqhgdc= github.com/google/go-github/v29 v29.0.3/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -15,61 +31,119 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 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/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= +github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/vrischmann/envconfig v1.2.0 h1:5/u4fI34/g3m0SdTQj/6f3r640jv9E5+yTXIZOWsxk0= github.com/vrischmann/envconfig v1.2.0/go.mod h1:c5DuUlkzfsnspy1g7qiqryPCsW+NjsrLsYq4zhwsoHo= +github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200320181102-891825fb96df h1:lDWgvUvNnaTnNBc/dwOty86cFeKoKWbwy2wQj0gIxbU= +golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 h1:kcXqo9vE6fsZY5X5Rd7R1l7fTgnWaDCVmln65REefiE= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8 h1:1+zQlQqEEhUeStBTi653GZAnAuivZq/2hz+Iz+OP7rg= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 h1:jIOcLT9BZzyJ9ce+IwwZ+aF9yeCqzrR+NrD68a/SHKw= golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae h1:3tcmuaB7wwSZtelmiv479UjUB+vviwABz7a133ZwOKQ= +golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544 h1:WJH1qsOB4/zb/li+zLMn0vaAUJ5FqPv6HYLI3aQVg1k= gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544/go.mod h1:UhTeH/yXCK/KY7TX24mqPkaQ7gZeqmWd/8SSS8B3aHw= +gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= +gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= +gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= +gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/hack/ci/run-test-integration.sh b/hack/ci/run-test-integration.sh new file mode 100755 index 0000000..038568f --- /dev/null +++ b/hack/ci/run-test-integration.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# standard bash error handling +set -o nounset # treat unset variables as an error and exit immediately. +set -o errexit # exit immediately when a command fails. +set -E # needs to be set if we want the ERR trap + +# Currently we are using the newest go (1.13) with go modules +export GO111MODULE=on + +readonly CURRENT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +readonly ROOT_PATH=${CURRENT_DIR}/../.. + +source "${CURRENT_DIR}/utilities.sh" || { echo 'Cannot load CI utilities.'; exit 1; } + +pushd ${ROOT_PATH} > /dev/null + +# Exit handler. This function is called anytime an EXIT signal is received. +# This function should never be explicitly called. +function _trap_exit () { + popd > /dev/null +} +trap _trap_exit EXIT + +echo -e "${INVERTED}" +echo "USER: " + ${USER} +echo "PATH: " + ${PATH} +echo "GOPATH:" + ${GOPATH} +echo -e "${NC}" + +## +# GO TEST INTEGRATION +## +shout "? go test integration" +go test ./tests/integration/... -v -tags=integration +# Check if tests passed +if [[ $? != 0 ]]; then + echo -e "${RED}✗ go test integration\n${NC}" + exit 1 +else echo -e "${GREEN}√ go test integration${NC}" +fi diff --git a/hack/ci/run-tests.sh b/hack/ci/run-test-unit.sh similarity index 98% rename from hack/ci/run-tests.sh rename to hack/ci/run-test-unit.sh index 2a0db19..ad695a5 100755 --- a/hack/ci/run-tests.sh +++ b/hack/ci/run-test-unit.sh @@ -40,7 +40,6 @@ if [ ! -z "$STATUS" ]; then else echo -e "${GREEN}√ go mod tidy${NC}" fi - ## # GO BUILD ## @@ -62,7 +61,7 @@ fi # GO TEST ## shout "? go test" -go test ./... +go test -race ./... # Check if tests passed if [[ $? != 0 ]]; then echo -e "${RED}✗ go test\n${NC}" diff --git a/internal/load/load.go b/internal/load/load.go index 16174ca..d66d831 100644 --- a/internal/load/load.go +++ b/internal/load/load.go @@ -58,12 +58,14 @@ func loadExperimentalChecks(experimentalChecks []string) ([]check.Checker, error var checks []check.Checker if contains(experimentalChecks, "notowned") { - var cfg check.NotOwnedFileConfig + var cfg struct { + NotOwnedChecker check.NotOwnedFileConfig + } if err := envconfig.Init(&cfg); err != nil { return nil, errors.Wrapf(err, "while loading config for %s", "notowned") } - checks = append(checks, check.NewNotOwnedFile(cfg)) + checks = append(checks, check.NewNotOwnedFile(cfg.NotOwnedChecker)) } return checks, nil diff --git a/tests/integration/helpers_test.go b/tests/integration/helpers_test.go new file mode 100644 index 0000000..51aa11b --- /dev/null +++ b/tests/integration/helpers_test.go @@ -0,0 +1,107 @@ +// +build integration + +package integration + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "os" + "os/exec" + "regexp" + "strings" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" +) + +func normalizeTimeDurations(in string) string { + duration := regexp.MustCompile(`\([0-9]+.[0-9]+(ns|us|µs|ms|s|m|h)\)`) + return duration.ReplaceAllString(in, "()") +} + +func CloneRepo(t *testing.T, url string, branch string) (string, func()) { + t.Helper() + + repoDir, err := ioutil.TempDir("", strings.ReplaceAll(t.Name(), "/", "-")) + require.NoError(t, err) + + cleanup := func() { + err = os.RemoveAll(repoDir) + require.NoError(t, err) + } + + _, err = git.PlainClone(repoDir, false, &git.CloneOptions{ + URL: url, + ReferenceName: plumbing.NewBranchReferenceName(branch), + SingleBranch: true, + Depth: 1, + }) + require.NoError(t, err) + + return repoDir, cleanup +} + +type Executor struct { + envs map[string]string + timeout time.Duration + binaryPath string +} + +func Exec() *Executor { + return &Executor{ + envs: map[string]string{}, + } +} + +// WithEnv adds given env. Overrides if previously existed +func (s *Executor) WithEnv(key string, value string) *Executor { + s.envs[key] = value + return s +} + +type ExecuteOutput struct { + Stdout string + ExitCode int +} + +func (s *Executor) AwaitResultAtMost(timeout time.Duration) (*ExecuteOutput, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + var stdout, stderr bytes.Buffer + + cmd := exec.CommandContext(ctx, s.binaryPath) + cmd.Stderr = &stderr + cmd.Stdout = &stdout + + for k, v := range s.envs { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) + } + + err := cmd.Run() + exitCode := cmd.ProcessState.ExitCode() + if exitCode != 0 && exitCode != 2 && exitCode != 3 { + return nil, errors.Wrapf(err, "while executing binary [stdout: %q] [stderr: %q]", + stdout.String(), stderr.String()) + } + return &ExecuteOutput{ + ExitCode: exitCode, + Stdout: stdout.String(), + }, nil +} + +func (s *Executor) WithTimeout(timeout time.Duration) *Executor { + s.timeout = timeout + return s +} + +func (s *Executor) Binary(binaryPath string) *Executor { + s.binaryPath = binaryPath + return s +} diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go new file mode 100644 index 0000000..1df911f --- /dev/null +++ b/tests/integration/integration_test.go @@ -0,0 +1,167 @@ +// +build integration + +package integration + +import ( + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gotest.tools/golden" +) + +const ( + binaryPathEnvName = "BINARY_PATH" + codeownersSamplesRepo = "https://github.com/gh-codeowners/codeowners-samples.git" +) + +// TestCheckHappyPath tests that codeowners-validator reports no issues for valid CODEOWNERS file. +// +// This test is based on golden file. +// If the `-test.update-golden` flag is set then the actual content is written +// to the golden file. +// +// To update golden file, run: +// go test ./tests/integration/... -v -test.update-golden -tags=integration -run=^TestCheckHappyPath$ +func TestCheckSuccess(t *testing.T) { + type Envs map[string]string + tests := []struct { + name string + envs Envs + }{ + { + name: "files", + envs: Envs{ + "CHECKS": "files", + }, + }, + { + name: "owners", + envs: Envs{ + "CHECKS": "owners", + "OWNER_CHECKER_REPOSITORY": "gh-codeowners/codeowners-samples", + "GITHUB_ACCESS_TOKEN": os.Getenv("GITHUB_TOKEN"), + }, + }, + { + name: "duppatterns", + envs: Envs{ + "CHECKS": "duppatterns", + }, + }, + { + name: "notowned", + envs: Envs{ + "PATH": os.Getenv("PATH"), // need to be set to find the `git` binary + "CHECKS": "disable-all", + "EXPERIMENTAL_CHECKS": "notowned", + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // given + repoDir, cleanup := CloneRepo(t, codeownersSamplesRepo, "happy-path") + defer cleanup() + + binaryPath := os.Getenv(binaryPathEnvName) + codeownersCmd := Exec(). + Binary(binaryPath). + // codeowners-validator basic config + WithEnv("REPOSITORY_PATH", repoDir) + + for k, v := range tc.envs { + codeownersCmd.WithEnv(k, v) + } + + // when + result, err := codeownersCmd.AwaitResultAtMost(3 * time.Minute) + + // then + require.NoError(t, err) + assert.Equal(t, result.ExitCode, 0) + normalizedOutput := normalizeTimeDurations(result.Stdout) + golden.Assert(t, normalizedOutput, t.Name()+".golden.txt") + }) + } +} + +// TestCheckFailures tests that codeowners-validator reports issues for not valid CODEOWNERS file. +// +// This test is based on golden file. +// If the `-test.update-golden` flag is set then the actual content is written +// to the golden file. +// +// To update golden file, run: +// go test ./tests/integration/... -v -test.update-golden -tags=integration -run=^TestCheckFailures$ +func TestCheckFailures(t *testing.T) { + type Envs map[string]string + tests := []struct { + name string + envs Envs + }{ + { + name: "files", + envs: Envs{ + "CHECKS": "files", + }, + }, + { + name: "owners", + envs: Envs{ + "CHECKS": "owners", + "OWNER_CHECKER_REPOSITORY": "gh-codeowners/codeowners-samples", + "GITHUB_ACCESS_TOKEN": os.Getenv("GITHUB_TOKEN"), + }, + }, + { + name: "duppatterns", + envs: Envs{ + "CHECKS": "duppatterns", + }, + }, + { + name: "notowned", + envs: Envs{ + "PATH": os.Getenv("PATH"), // need to be set to find the `git` binary + "CHECKS": "disable-all", + "EXPERIMENTAL_CHECKS": "notowned", + "NOT_OWNED_CHECKER_SKIP_PATTERNS": "*", + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // given + repoDir, cleanup := CloneRepo(t, codeownersSamplesRepo, "failures") + defer cleanup() + + binaryPath := os.Getenv(binaryPathEnvName) + + codeownersCmd := Exec(). + Binary(binaryPath). + // codeowners-validator basic config + WithEnv("REPOSITORY_PATH", repoDir) + + for k, v := range tc.envs { + codeownersCmd.WithEnv(k, v) + } + + // when + result, err := codeownersCmd.AwaitResultAtMost(3 * time.Minute) + + // then + require.NoError(t, err) + assert.Equal(t, 3, result.ExitCode) + + normalizedOutput := normalizeTimeDurations(result.Stdout) + golden.Assert(t, normalizedOutput, t.Name()+".golden.txt") + }) + } +} + +func TestMultipleChecksSuccess(t *testing.T) { + t.Skip("not implemented yet") +} diff --git a/tests/integration/testdata/TestCheckFailures/duppatterns.golden.txt b/tests/integration/testdata/TestCheckFailures/duppatterns.golden.txt new file mode 100644 index 0000000..612c30f --- /dev/null +++ b/tests/integration/testdata/TestCheckFailures/duppatterns.golden.txt @@ -0,0 +1,6 @@ +==> Executing Duplicated Pattern Checker () + [err] Pattern "/some/awesome/dir" is defined 2 times in lines: + * 10: with owners: [@mszostok @owner-a] + * 11: with owners: [@octocat] + +1 check(s) executed, 1 failure(s) diff --git a/tests/integration/testdata/TestCheckFailures/files.golden.txt b/tests/integration/testdata/TestCheckFailures/files.golden.txt new file mode 100644 index 0000000..ebe0258 --- /dev/null +++ b/tests/integration/testdata/TestCheckFailures/files.golden.txt @@ -0,0 +1,4 @@ +==> Executing File Exist Checker () + [err] line 13: "/this-folder-does-not-exits/really" does not match any files in repository + +1 check(s) executed, 1 failure(s) diff --git a/tests/integration/testdata/TestCheckFailures/notowned.golden.txt b/tests/integration/testdata/TestCheckFailures/notowned.golden.txt new file mode 100644 index 0000000..5e4f0f7 --- /dev/null +++ b/tests/integration/testdata/TestCheckFailures/notowned.golden.txt @@ -0,0 +1,7 @@ +==> Executing [Experimental] Not Owned File Checker () + [err] Found 3 not owned files (skipped patterns: "*"): + * .github/workflows/cron.yml + * .gitignore + * CODEOWNERS + +1 check(s) executed, 1 failure(s) diff --git a/tests/integration/testdata/TestCheckFailures/owners.golden.txt b/tests/integration/testdata/TestCheckFailures/owners.golden.txt new file mode 100644 index 0000000..1ded994 --- /dev/null +++ b/tests/integration/testdata/TestCheckFailures/owners.golden.txt @@ -0,0 +1,42 @@ +==> Executing Valid Owner Checker () + [err] line 10: User "@owner-a" does not have github account + [err] line 11: User "@octocat" is not a member of the organization + [err] line 15: Team "amigos" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "avengers" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "bannermen" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "best-of-the-best" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "bosses" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "champions" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "crew" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "dominators" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "dream-team" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "elite" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "force" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "goal-diggers" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "heatwave" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "hot-shots" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "hustle" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "icons" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "justice-league" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "legends" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "lightning" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "masters" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "monarchy" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "naturals" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "outliers" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "peak-performers" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "power" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "rebels" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "revolution" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "ringmasters" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "rule-breakers" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "shakedown" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "squad" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "titans" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "tribe" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "united" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "warriors" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 15: Team "wolf-pack" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + [err] line 17: Team "not-existing-team" does not exist in organization "gh-codeowners" or has no permissions associated with the repository. + +1 check(s) executed, 1 failure(s) diff --git a/tests/integration/testdata/TestCheckSuccess/duppatterns.golden.txt b/tests/integration/testdata/TestCheckSuccess/duppatterns.golden.txt new file mode 100644 index 0000000..2c067ae --- /dev/null +++ b/tests/integration/testdata/TestCheckSuccess/duppatterns.golden.txt @@ -0,0 +1,4 @@ +==> Executing Duplicated Pattern Checker () + Check OK + +1 check(s) executed, no failure(s) diff --git a/tests/integration/testdata/TestCheckSuccess/files.golden.txt b/tests/integration/testdata/TestCheckSuccess/files.golden.txt new file mode 100644 index 0000000..400e347 --- /dev/null +++ b/tests/integration/testdata/TestCheckSuccess/files.golden.txt @@ -0,0 +1,4 @@ +==> Executing File Exist Checker () + Check OK + +1 check(s) executed, no failure(s) diff --git a/tests/integration/testdata/TestCheckSuccess/notowned.golden.txt b/tests/integration/testdata/TestCheckSuccess/notowned.golden.txt new file mode 100644 index 0000000..038290f --- /dev/null +++ b/tests/integration/testdata/TestCheckSuccess/notowned.golden.txt @@ -0,0 +1,4 @@ +==> Executing [Experimental] Not Owned File Checker () + Check OK + +1 check(s) executed, no failure(s) diff --git a/tests/integration/testdata/TestCheckSuccess/owners.golden.txt b/tests/integration/testdata/TestCheckSuccess/owners.golden.txt new file mode 100644 index 0000000..e93cda6 --- /dev/null +++ b/tests/integration/testdata/TestCheckSuccess/owners.golden.txt @@ -0,0 +1,4 @@ +==> Executing Valid Owner Checker () + Check OK + +1 check(s) executed, no failure(s)