├── .dockerignore ├── .drone.jsonnet ├── .drone.windows.jsonnet ├── .drone.windows.yml ├── .drone.yml ├── .github ├── issue_template.md ├── pull_request_template.md └── settings.yml ├── .gitignore ├── README.md ├── client ├── client.go ├── interface.go └── types.go ├── cmd_cobertura.go ├── cmd_gocov.go ├── cmd_lcov.go ├── cmd_publish.go ├── cmd_publish_test.go ├── coverage ├── cobertura │ ├── cobertura.go │ └── cobertura_test.go ├── coverage.go ├── coverage_test.go ├── gocov │ ├── gocov.go │ └── gocov_test.go ├── jacoco │ └── jacoco.go ├── lcov │ ├── lcov.go │ └── lcov_test.go ├── path.go └── path_test.go ├── docker ├── Dockerfile.linux.amd64 ├── Dockerfile.linux.arm ├── Dockerfile.linux.arm64 ├── Dockerfile.windows.1803 ├── Dockerfile.windows.1809 └── manifest.tmpl ├── go.mod ├── go.sum ├── main.go ├── merge.go └── pipeline.libsonnet /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !release/ 3 | -------------------------------------------------------------------------------- /.drone.jsonnet: -------------------------------------------------------------------------------- 1 | local pipeline = import 'pipeline.libsonnet'; 2 | local name = 'drone-coverage'; 3 | 4 | [ 5 | pipeline.test('linux', 'amd64'), 6 | pipeline.build(name, 'linux', 'amd64'), 7 | pipeline.build(name, 'linux', 'arm64'), 8 | pipeline.build(name, 'linux', 'arm'), 9 | pipeline.notifications(depends_on=[ 10 | 'linux-amd64', 11 | 'linux-arm64', 12 | 'linux-arm', 13 | ]), 14 | ] 15 | -------------------------------------------------------------------------------- /.drone.windows.jsonnet: -------------------------------------------------------------------------------- 1 | local pipeline = import 'pipeline.libsonnet'; 2 | local name = 'drone-coverage'; 3 | 4 | [ 5 | pipeline.test('windows', 'amd64', '1803'), 6 | pipeline.build(name, 'windows', 'amd64', '1803'), 7 | pipeline.build(name, 'windows', 'amd64', '1809'), 8 | pipeline.notifications('windows', 'amd64', '1809', ['windows-1803', 'windows-1809']), 9 | ] 10 | -------------------------------------------------------------------------------- /.drone.windows.yml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: pipeline 3 | name: testing 4 | 5 | platform: 6 | os: windows 7 | arch: amd64 8 | version: 1803 9 | 10 | steps: 11 | - name: vet 12 | pull: always 13 | image: golang:1.11-windowsservercore-1803 14 | commands: 15 | - go vet ./... 16 | environment: 17 | GO111MODULE: on 18 | volumes: 19 | - name: gopath 20 | path: C:\\gopath 21 | 22 | - name: test 23 | pull: always 24 | image: golang:1.11-windowsservercore-1803 25 | commands: 26 | - go test -cover ./... 27 | environment: 28 | GO111MODULE: on 29 | volumes: 30 | - name: gopath 31 | path: C:\\gopath 32 | 33 | volumes: 34 | - name: gopath 35 | temp: {} 36 | 37 | trigger: 38 | ref: 39 | - refs/heads/master 40 | - "refs/tags/**" 41 | - "refs/pull/**" 42 | 43 | --- 44 | kind: pipeline 45 | name: windows-1803 46 | 47 | platform: 48 | os: windows 49 | arch: amd64 50 | version: 1803 51 | 52 | steps: 53 | - name: build-push 54 | pull: always 55 | image: golang:1.11-windowsservercore-1803 56 | commands: 57 | - "go build -v -ldflags \"-X main.version=${DRONE_COMMIT_SHA:0:8}\" -a -tags netgo -o release/windows/amd64/drone-coverage.exe" 58 | environment: 59 | CGO_ENABLED: 0 60 | GO111MODULE: on 61 | when: 62 | event: 63 | exclude: 64 | - tag 65 | 66 | - name: build-tag 67 | pull: always 68 | image: golang:1.11-windowsservercore-1803 69 | commands: 70 | - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v}\" -a -tags netgo -o release/windows/amd64/drone-coverage.exe" 71 | environment: 72 | CGO_ENABLED: 0 73 | GO111MODULE: on 74 | when: 75 | event: 76 | - tag 77 | 78 | - name: executable 79 | pull: always 80 | image: golang:1.11-windowsservercore-1803 81 | commands: 82 | - ./release/windows/amd64/drone-coverage.exe --help 83 | 84 | - name: dryrun 85 | pull: always 86 | image: plugins/docker:windows-1803 87 | settings: 88 | daemon_off: true 89 | dockerfile: docker/Dockerfile.windows.1803 90 | dry_run: true 91 | password: 92 | from_secret: docker_password 93 | repo: plugins/coverage 94 | tags: windows-1803 95 | username: 96 | from_secret: docker_username 97 | volumes: 98 | - name: docker_pipe 99 | path: \\\\.\\pipe\\docker_engine 100 | when: 101 | event: 102 | - pull_request 103 | 104 | - name: publish 105 | pull: always 106 | image: plugins/docker:windows-1803 107 | settings: 108 | auto_tag: true 109 | auto_tag_suffix: windows-1803 110 | daemon_off: true 111 | dockerfile: docker/Dockerfile.windows.1803 112 | password: 113 | from_secret: docker_password 114 | repo: plugins/coverage 115 | username: 116 | from_secret: docker_username 117 | volumes: 118 | - name: docker_pipe 119 | path: \\\\.\\pipe\\docker_engine 120 | when: 121 | event: 122 | exclude: 123 | - pull_request 124 | 125 | volumes: 126 | - name: docker_pipe 127 | host: 128 | path: \\\\.\\pipe\\docker_engine 129 | 130 | trigger: 131 | ref: 132 | - refs/heads/master 133 | - "refs/tags/**" 134 | - "refs/pull/**" 135 | 136 | depends_on: 137 | - testing 138 | 139 | --- 140 | kind: pipeline 141 | name: windows-1809 142 | 143 | platform: 144 | os: windows 145 | arch: amd64 146 | version: 1809 147 | 148 | steps: 149 | - name: build-push 150 | pull: always 151 | image: golang:1.11-windowsservercore-1809 152 | commands: 153 | - "go build -v -ldflags \"-X main.version=${DRONE_COMMIT_SHA:0:8}\" -a -tags netgo -o release/windows/amd64/drone-coverage.exe" 154 | environment: 155 | CGO_ENABLED: 0 156 | GO111MODULE: on 157 | when: 158 | event: 159 | exclude: 160 | - tag 161 | 162 | - name: build-tag 163 | pull: always 164 | image: golang:1.11-windowsservercore-1809 165 | commands: 166 | - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v}\" -a -tags netgo -o release/windows/amd64/drone-coverage.exe" 167 | environment: 168 | CGO_ENABLED: 0 169 | GO111MODULE: on 170 | when: 171 | event: 172 | - tag 173 | 174 | - name: executable 175 | pull: always 176 | image: golang:1.11-windowsservercore-1809 177 | commands: 178 | - ./release/windows/amd64/drone-coverage.exe --help 179 | 180 | - name: dryrun 181 | pull: always 182 | image: plugins/docker:windows-1809 183 | settings: 184 | daemon_off: true 185 | dockerfile: docker/Dockerfile.windows.1809 186 | dry_run: true 187 | password: 188 | from_secret: docker_password 189 | repo: plugins/coverage 190 | tags: windows-1809 191 | username: 192 | from_secret: docker_username 193 | volumes: 194 | - name: docker_pipe 195 | path: \\\\.\\pipe\\docker_engine 196 | when: 197 | event: 198 | - pull_request 199 | 200 | - name: publish 201 | pull: always 202 | image: plugins/docker:windows-1809 203 | settings: 204 | auto_tag: true 205 | auto_tag_suffix: windows-1809 206 | daemon_off: true 207 | dockerfile: docker/Dockerfile.windows.1809 208 | password: 209 | from_secret: docker_password 210 | repo: plugins/coverage 211 | username: 212 | from_secret: docker_username 213 | volumes: 214 | - name: docker_pipe 215 | path: \\\\.\\pipe\\docker_engine 216 | when: 217 | event: 218 | exclude: 219 | - pull_request 220 | 221 | volumes: 222 | - name: docker_pipe 223 | host: 224 | path: \\\\.\\pipe\\docker_engine 225 | 226 | trigger: 227 | ref: 228 | - refs/heads/master 229 | - "refs/tags/**" 230 | - "refs/pull/**" 231 | 232 | depends_on: 233 | - testing 234 | 235 | --- 236 | kind: pipeline 237 | name: notifications 238 | 239 | platform: 240 | os: windows 241 | arch: amd64 242 | version: 1809 243 | 244 | steps: 245 | - name: manifest 246 | pull: always 247 | image: plugins/manifest 248 | settings: 249 | auto_tag: true 250 | ignore_missing: true 251 | password: 252 | from_secret: docker_password 253 | spec: docker/manifest.tmpl 254 | username: 255 | from_secret: docker_username 256 | 257 | - name: microbadger 258 | pull: always 259 | image: plugins/webhook 260 | settings: 261 | urls: 262 | from_secret: microbadger_url 263 | 264 | trigger: 265 | ref: 266 | - refs/heads/master 267 | - "refs/tags/**" 268 | 269 | depends_on: 270 | - windows-1803 271 | - windows-1809 272 | 273 | ... 274 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: pipeline 3 | name: testing 4 | 5 | platform: 6 | os: linux 7 | arch: amd64 8 | 9 | steps: 10 | - name: vet 11 | pull: always 12 | image: golang:1.11 13 | commands: 14 | - go vet ./... 15 | environment: 16 | GO111MODULE: on 17 | volumes: 18 | - name: gopath 19 | path: /go 20 | 21 | - name: test 22 | pull: always 23 | image: golang:1.11 24 | commands: 25 | - go test -cover ./... 26 | environment: 27 | GO111MODULE: on 28 | volumes: 29 | - name: gopath 30 | path: /go 31 | 32 | volumes: 33 | - name: gopath 34 | temp: {} 35 | 36 | trigger: 37 | ref: 38 | - refs/heads/master 39 | - "refs/tags/**" 40 | - "refs/pull/**" 41 | 42 | --- 43 | kind: pipeline 44 | name: linux-amd64 45 | 46 | platform: 47 | os: linux 48 | arch: amd64 49 | 50 | steps: 51 | - name: build-push 52 | pull: always 53 | image: golang:1.11 54 | commands: 55 | - "go build -v -ldflags \"-X main.version=${DRONE_COMMIT_SHA:0:8}\" -a -tags netgo -o release/linux/amd64/drone-coverage" 56 | environment: 57 | CGO_ENABLED: 0 58 | GO111MODULE: on 59 | when: 60 | event: 61 | exclude: 62 | - tag 63 | 64 | - name: build-tag 65 | pull: always 66 | image: golang:1.11 67 | commands: 68 | - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v}\" -a -tags netgo -o release/linux/amd64/drone-coverage" 69 | environment: 70 | CGO_ENABLED: 0 71 | GO111MODULE: on 72 | when: 73 | event: 74 | - tag 75 | 76 | - name: executable 77 | pull: always 78 | image: golang:1.11 79 | commands: 80 | - ./release/linux/amd64/drone-coverage --help 81 | 82 | - name: dryrun 83 | pull: always 84 | image: plugins/docker:linux-amd64 85 | settings: 86 | daemon_off: false 87 | dockerfile: docker/Dockerfile.linux.amd64 88 | dry_run: true 89 | password: 90 | from_secret: docker_password 91 | repo: plugins/coverage 92 | tags: linux-amd64 93 | username: 94 | from_secret: docker_username 95 | when: 96 | event: 97 | - pull_request 98 | 99 | - name: publish 100 | pull: always 101 | image: plugins/docker:linux-amd64 102 | settings: 103 | auto_tag: true 104 | auto_tag_suffix: linux-amd64 105 | daemon_off: false 106 | dockerfile: docker/Dockerfile.linux.amd64 107 | password: 108 | from_secret: docker_password 109 | repo: plugins/coverage 110 | username: 111 | from_secret: docker_username 112 | when: 113 | event: 114 | exclude: 115 | - pull_request 116 | 117 | trigger: 118 | ref: 119 | - refs/heads/master 120 | - "refs/tags/**" 121 | - "refs/pull/**" 122 | 123 | depends_on: 124 | - testing 125 | 126 | --- 127 | kind: pipeline 128 | name: linux-arm64 129 | 130 | platform: 131 | os: linux 132 | arch: arm64 133 | 134 | steps: 135 | - name: build-push 136 | pull: always 137 | image: golang:1.11 138 | commands: 139 | - "go build -v -ldflags \"-X main.version=${DRONE_COMMIT_SHA:0:8}\" -a -tags netgo -o release/linux/arm64/drone-coverage" 140 | environment: 141 | CGO_ENABLED: 0 142 | GO111MODULE: on 143 | when: 144 | event: 145 | exclude: 146 | - tag 147 | 148 | - name: build-tag 149 | pull: always 150 | image: golang:1.11 151 | commands: 152 | - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v}\" -a -tags netgo -o release/linux/arm64/drone-coverage" 153 | environment: 154 | CGO_ENABLED: 0 155 | GO111MODULE: on 156 | when: 157 | event: 158 | - tag 159 | 160 | - name: executable 161 | pull: always 162 | image: golang:1.11 163 | commands: 164 | - ./release/linux/arm64/drone-coverage --help 165 | 166 | - name: dryrun 167 | pull: always 168 | image: plugins/docker:linux-arm64 169 | settings: 170 | daemon_off: false 171 | dockerfile: docker/Dockerfile.linux.arm64 172 | dry_run: true 173 | password: 174 | from_secret: docker_password 175 | repo: plugins/coverage 176 | tags: linux-arm64 177 | username: 178 | from_secret: docker_username 179 | when: 180 | event: 181 | - pull_request 182 | 183 | - name: publish 184 | pull: always 185 | image: plugins/docker:linux-arm64 186 | settings: 187 | auto_tag: true 188 | auto_tag_suffix: linux-arm64 189 | daemon_off: false 190 | dockerfile: docker/Dockerfile.linux.arm64 191 | password: 192 | from_secret: docker_password 193 | repo: plugins/coverage 194 | username: 195 | from_secret: docker_username 196 | when: 197 | event: 198 | exclude: 199 | - pull_request 200 | 201 | trigger: 202 | ref: 203 | - refs/heads/master 204 | - "refs/tags/**" 205 | - "refs/pull/**" 206 | 207 | depends_on: 208 | - testing 209 | 210 | --- 211 | kind: pipeline 212 | name: linux-arm 213 | 214 | platform: 215 | os: linux 216 | arch: arm 217 | 218 | steps: 219 | - name: build-push 220 | pull: always 221 | image: golang:1.11 222 | commands: 223 | - "go build -v -ldflags \"-X main.version=${DRONE_COMMIT_SHA:0:8}\" -a -tags netgo -o release/linux/arm/drone-coverage" 224 | environment: 225 | CGO_ENABLED: 0 226 | GO111MODULE: on 227 | when: 228 | event: 229 | exclude: 230 | - tag 231 | 232 | - name: build-tag 233 | pull: always 234 | image: golang:1.11 235 | commands: 236 | - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v}\" -a -tags netgo -o release/linux/arm/drone-coverage" 237 | environment: 238 | CGO_ENABLED: 0 239 | GO111MODULE: on 240 | when: 241 | event: 242 | - tag 243 | 244 | - name: executable 245 | pull: always 246 | image: golang:1.11 247 | commands: 248 | - ./release/linux/arm/drone-coverage --help 249 | 250 | - name: dryrun 251 | pull: always 252 | image: plugins/docker:linux-arm 253 | settings: 254 | daemon_off: false 255 | dockerfile: docker/Dockerfile.linux.arm 256 | dry_run: true 257 | password: 258 | from_secret: docker_password 259 | repo: plugins/coverage 260 | tags: linux-arm 261 | username: 262 | from_secret: docker_username 263 | when: 264 | event: 265 | - pull_request 266 | 267 | - name: publish 268 | pull: always 269 | image: plugins/docker:linux-arm 270 | settings: 271 | auto_tag: true 272 | auto_tag_suffix: linux-arm 273 | daemon_off: false 274 | dockerfile: docker/Dockerfile.linux.arm 275 | password: 276 | from_secret: docker_password 277 | repo: plugins/coverage 278 | username: 279 | from_secret: docker_username 280 | when: 281 | event: 282 | exclude: 283 | - pull_request 284 | 285 | trigger: 286 | ref: 287 | - refs/heads/master 288 | - "refs/tags/**" 289 | - "refs/pull/**" 290 | 291 | depends_on: 292 | - testing 293 | 294 | --- 295 | kind: pipeline 296 | name: notifications 297 | 298 | platform: 299 | os: linux 300 | arch: amd64 301 | 302 | steps: 303 | - name: manifest 304 | pull: always 305 | image: plugins/manifest 306 | settings: 307 | auto_tag: true 308 | ignore_missing: true 309 | password: 310 | from_secret: docker_password 311 | spec: docker/manifest.tmpl 312 | username: 313 | from_secret: docker_username 314 | 315 | - name: microbadger 316 | pull: always 317 | image: plugins/webhook 318 | settings: 319 | urls: 320 | from_secret: microbadger_url 321 | 322 | trigger: 323 | ref: 324 | - refs/heads/master 325 | - "refs/tags/**" 326 | 327 | depends_on: 328 | - linux-amd64 329 | - linux-arm64 330 | - linux-arm 331 | 332 | ... 333 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drone-plugins/drone-coverage/56d79b9f3913c306413d8a3bbefe6d816d333e49/.github/pull_request_template.md -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | name: drone-coverage 3 | description: Plugin for publishing coverage reports 4 | homepage: http://plugins.drone.io/drone-plugins/drone-coverage 5 | topics: drone, drone-plugin 6 | 7 | private: false 8 | has_issues: true 9 | has_wiki: false 10 | has_downloads: false 11 | 12 | default_branch: master 13 | 14 | allow_squash_merge: true 15 | allow_merge_commit: true 16 | allow_rebase_merge: true 17 | 18 | labels: 19 | - name: bug 20 | color: d73a4a 21 | description: Something isn't working 22 | - name: duplicate 23 | color: cfd3d7 24 | description: This issue or pull request already exists 25 | - name: enhancement 26 | color: a2eeef 27 | description: New feature or request 28 | - name: good first issue 29 | color: 7057ff 30 | description: Good for newcomers 31 | - name: help wanted 32 | color: 008672 33 | description: Extra attention is needed 34 | - name: invalid 35 | color: e4e669 36 | description: This doesn't seem right 37 | - name: question 38 | color: d876e3 39 | description: Further information is requested 40 | - name: renovate 41 | color: e99695 42 | description: Automated action from Renovate 43 | - name: wontfix 44 | color: ffffff 45 | description: This will not be worked on 46 | 47 | teams: 48 | - name: Admins 49 | permission: admin 50 | - name: Captain 51 | permission: admin 52 | - name: Maintainers 53 | permission: push 54 | 55 | branches: 56 | - name: master 57 | protection: 58 | required_pull_request_reviews: 59 | required_approving_review_count: 1 60 | dismiss_stale_reviews: false 61 | require_code_owner_reviews: false 62 | dismissal_restrictions: 63 | teams: 64 | - Admins 65 | - Captain 66 | required_status_checks: 67 | strict: true 68 | contexts: 69 | - continuous-integration/drone/pr 70 | enforce_admins: false 71 | restrictions: 72 | users: [] 73 | teams: [] 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | release/ 27 | vendor/ 28 | 29 | coverage.out 30 | drone-coverage 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drone-coverage 2 | 3 | [![Build Status](http://cloud.drone.io/api/badges/drone-plugins/drone-coverage/status.svg)](http://cloud.drone.io/drone-plugins/drone-coverage) 4 | [![Gitter chat](https://badges.gitter.im/drone/drone.png)](https://gitter.im/drone/drone) 5 | [![Join the discussion at https://discourse.drone.io](https://img.shields.io/badge/discourse-forum-orange.svg)](https://discourse.drone.io) 6 | [![Drone questions at https://stackoverflow.com](https://img.shields.io/badge/drone-stackoverflow-orange.svg)](https://stackoverflow.com/questions/tagged/drone.io) 7 | [![](https://images.microbadger.com/badges/image/plugins/coverage.svg)](https://microbadger.com/images/plugins/coverage "Get your own image badge on microbadger.com") 8 | [![Go Doc](https://godoc.org/github.com/drone-plugins/drone-coverage?status.svg)](http://godoc.org/github.com/drone-plugins/drone-coverage) 9 | [![Go Report](https://goreportcard.com/badge/github.com/drone-plugins/drone-coverage)](https://goreportcard.com/report/github.com/drone-plugins/drone-coverage) 10 | 11 | > Warning: This plugin has not been migrated to Drone >= 0.5 yet, you are not able to use it with a current Drone version until somebody volunteers to update the plugin structure to the new format. 12 | 13 | Drone plugin for publishing coverage reports. For the usage information and a listing of the available options please take a look at [the docs](http://plugins.drone.io/drone-plugins/drone-coverage/). 14 | 15 | ## Build 16 | 17 | Build the binary with the following command: 18 | 19 | ```console 20 | export GOOS=linux 21 | export GOARCH=amd64 22 | export CGO_ENABLED=0 23 | export GO111MODULE=on 24 | 25 | go build -v -a -tags netgo -o release/linux/amd64/drone-coverage 26 | ``` 27 | 28 | ## Docker 29 | 30 | Build the Docker image with the following command: 31 | 32 | ```console 33 | docker build \ 34 | --label org.label-schema.build-date=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ 35 | --label org.label-schema.vcs-ref=$(git rev-parse --short HEAD) \ 36 | --file docker/Dockerfile.linux.amd64 --tag plugins/coverage . 37 | ``` 38 | 39 | ## Usage 40 | 41 | ```sh 42 | docker run --rm \ 43 | -e DRONE_REPO=octocat/hello-world \ 44 | -e DRONE_COMMIT_SHA=7fd1a60b01f91b314f59955a4e4d4e80d8edf11d \ 45 | -e DRONE_COMMIT_REF=refs/heads/master \ 46 | -e DRONE_COMMIT_BRANCH=master \ 47 | -e DRONE_COMMIT_AUTHOR=octocat \ 48 | -e DRONE_BUILD_NUMBER=1 \ 49 | -e DRONE_BUILD_EVENT=push \ 50 | -e DRONE_BUILD_STATUS=success \ 51 | -e DRONE_BUILD_LINK=http://github.com/octocat/hello-world \ 52 | -e PLUGIN_PATTERN="path/to/lcov.info" \ 53 | -e PLUGIN_SERVER="http://coverage.server.com" \ 54 | -e GITHUB_TOKEN=3da541559918a808c2402b \ 55 | -v $(pwd):$(pwd) \ 56 | -w $(pwd) \ 57 | plugins/coverage 58 | ``` 59 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | "net/url" 12 | 13 | "golang.org/x/oauth2" 14 | ) 15 | 16 | const ( 17 | pathLogin = "%s/login?access_token=%s" 18 | pathUser = "%s/api/user" 19 | pathRepos = "%s/api/user/repos" 20 | pathRepo = "%s/api/repos/%s" 21 | pathBuilds = "%s/api/repos/%s/builds" 22 | ) 23 | 24 | type client struct { 25 | client *http.Client 26 | base string // base url 27 | } 28 | 29 | // NewClient returns a client at the specified url. 30 | func NewClient(uri string) Client { 31 | return &client{http.DefaultClient, uri} 32 | } 33 | 34 | func NewClientTLS(uri string, tlsConfig *tls.Config) Client { 35 | httpClient := http.DefaultClient 36 | if tlsConfig != nil { 37 | transport := &http.Transport{TLSClientConfig: tlsConfig} 38 | httpClient = &http.Client{Transport: transport} 39 | } 40 | return &client{httpClient, uri} 41 | } 42 | 43 | // NewClientToken returns a client at the specified url that 44 | // authenticates all outbound requests with the given token. 45 | func NewClientToken(uri, token string) Client { 46 | config := new(oauth2.Config) 47 | auther := config.Client(oauth2.NoContext, &oauth2.Token{AccessToken: token}) 48 | return &client{auther, uri} 49 | } 50 | 51 | // NewClientTokenTLS returns a client at the specified url that 52 | // authenticates all outbound requests with the given token and 53 | // tls.Config if provided. 54 | func NewClientTokenTLS(uri, token string, tlsConfig *tls.Config) Client { 55 | config := new(oauth2.Config) 56 | auther := config.Client(oauth2.NoContext, &oauth2.Token{AccessToken: token}) 57 | if tlsConfig != nil { 58 | auther.Transport.(*oauth2.Transport).Base = &http.Transport{TLSClientConfig: tlsConfig} 59 | } 60 | return &client{auther, uri} 61 | } 62 | 63 | func (c *client) Token(token string) (*Token, error) { 64 | out := new(Token) 65 | uri := fmt.Sprintf(pathLogin, c.base, token) 66 | err := c.post(uri, nil, out) 67 | return out, err 68 | } 69 | 70 | func (c *client) Repo(repo string) (*Repo, error) { 71 | out := new(Repo) 72 | uri := fmt.Sprintf(pathRepo, c.base, repo) 73 | err := c.get(uri, out) 74 | return out, err 75 | } 76 | 77 | func (c *client) Activate(repo string) (*Repo, error) { 78 | out := new(Repo) 79 | uri := fmt.Sprintf(pathRepo, c.base, repo) 80 | err := c.post(uri, nil, out) 81 | return out, err 82 | } 83 | 84 | func (c *client) Deactivate(repo string) error { 85 | uri := fmt.Sprintf(pathRepo, c.base, repo) 86 | err := c.delete(uri) 87 | return err 88 | } 89 | 90 | func (c *client) Submit(repo string, build *Build, report *Report) (*Build, error) { 91 | in := struct { 92 | Report *Report `json:"report"` 93 | Build *Build `json:"build"` 94 | }{report, build} 95 | 96 | out := new(Build) 97 | uri := fmt.Sprintf(pathBuilds, c.base, repo) 98 | err := c.post(uri, &in, out) 99 | return out, err 100 | } 101 | 102 | // 103 | // http request helper functions 104 | // 105 | 106 | // helper function for making an http GET request. 107 | func (c *client) get(rawurl string, out interface{}) error { 108 | return c.do(rawurl, "GET", nil, out) 109 | } 110 | 111 | // helper function for making an http POST request. 112 | func (c *client) post(rawurl string, in, out interface{}) error { 113 | return c.do(rawurl, "POST", in, out) 114 | } 115 | 116 | // helper function for making an http PUT request. 117 | func (c *client) put(rawurl string, in, out interface{}) error { 118 | return c.do(rawurl, "PUT", in, out) 119 | } 120 | 121 | // helper function for making an http PATCH request. 122 | func (c *client) patch(rawurl string, in, out interface{}) error { 123 | return c.do(rawurl, "PATCH", in, out) 124 | } 125 | 126 | // helper function for making an http DELETE request. 127 | func (c *client) delete(rawurl string) error { 128 | return c.do(rawurl, "DELETE", nil, nil) 129 | } 130 | 131 | // helper function to make an http request 132 | func (c *client) do(rawurl, method string, in, out interface{}) error { 133 | // executes the http request and returns the body as 134 | // and io.ReadCloser 135 | body, err := c.stream(rawurl, method, in, out) 136 | if err != nil { 137 | return err 138 | } 139 | defer body.Close() 140 | 141 | // if a json response is expected, parse and return 142 | // the json response. 143 | if out != nil { 144 | return json.NewDecoder(body).Decode(out) 145 | } 146 | return nil 147 | } 148 | 149 | // helper function to stream an http request 150 | func (c *client) stream(rawurl, method string, in, out interface{}) (io.ReadCloser, error) { 151 | uri, err := url.Parse(rawurl) 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | // if we are posting or putting data, we need to 157 | // write it to the body of the request. 158 | var buf io.ReadWriter 159 | if in != nil { 160 | buf = new(bytes.Buffer) 161 | err := json.NewEncoder(buf).Encode(in) 162 | if err != nil { 163 | return nil, err 164 | } 165 | } 166 | 167 | // creates a new http request to bitbucket. 168 | req, err := http.NewRequest(method, uri.String(), buf) 169 | if err != nil { 170 | return nil, err 171 | } 172 | if in != nil { 173 | req.Header.Set("Content-Type", "application/json") 174 | } 175 | 176 | resp, err := c.client.Do(req) 177 | if err != nil { 178 | return nil, err 179 | } 180 | if resp.StatusCode > http.StatusPartialContent { 181 | defer resp.Body.Close() 182 | out, _ := ioutil.ReadAll(resp.Body) 183 | return nil, fmt.Errorf(string(out)) 184 | } 185 | return resp.Body, nil 186 | } 187 | -------------------------------------------------------------------------------- /client/interface.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // Client to access the remote APIs. 4 | type Client interface { 5 | Token(string) (*Token, error) 6 | Repo(string) (*Repo, error) 7 | Activate(string) (*Repo, error) 8 | Deactivate(string) error 9 | Submit(string, *Build, *Report) (*Build, error) 10 | } 11 | -------------------------------------------------------------------------------- /client/types.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // Token represents a user oauth2 token. 4 | type Token struct { 5 | Access string `json:"access_token"` 6 | Refresh string `json:"refresh_token"` 7 | Expires int64 `json:"expires_in"` 8 | } 9 | 10 | // User represents a registered user. 11 | type User struct { 12 | Login string `json:"login"` 13 | Avatar string `json:"avatar_url"` 14 | } 15 | 16 | // Repo represents a remote version control repository 17 | // for which coverage data has been collected. 18 | type Repo struct { 19 | Owner string `json:"owner"` 20 | Name string `json:"name"` 21 | Slug string `json:"slug"` 22 | Link string `json:"link"` 23 | Branch string `json:"branch"` 24 | Avatar string `json:"avatar_url"` 25 | Private bool `json:"private"` 26 | Coverage float64 `json:"coverage_percent"` 27 | Delta float64 `json:"coverage_changed"` 28 | Covered int64 `json:"lines_covered"` 29 | Lines int64 `json:"lines_total"` 30 | } 31 | 32 | // Build represents a build in an external continuous integration 33 | // server for which coverage data has been collected. 34 | type Build struct { 35 | Number int `json:"number"` 36 | Event string `json:"event"` 37 | Commit string `json:"commit"` 38 | Branch string `json:"branch"` 39 | Ref string `json:"ref"` 40 | Refspec string `json:"refspec"` 41 | Message string `json:"message"` 42 | Author string `json:"author"` 43 | Avatar string `json:"author_avatar"` 44 | Timestamp int64 `json:"timestamp"` 45 | Link string `json:"link_url"` 46 | Coverage float64 `json:"coverage_percent"` 47 | Changed float64 `json:"coverage_changed"` 48 | Covered int64 `json:"lines_covered"` 49 | Lines int64 `json:"lines_total"` 50 | } 51 | 52 | // File represents a source file from your repository that 53 | // includes coverage data per line. 54 | type File struct { 55 | FileName string `json:"filename"` 56 | Coverage float64 `json:"coverage_percent"` 57 | Changed float64 `json:"coverage_changed"` 58 | Covered int64 `json:"lines_covered"` 59 | Lines int64 `json:"lines_total"` 60 | Mode string `json:"coverage_mode"` 61 | 62 | Blocks []*Block `json:"blocks"` 63 | } 64 | 65 | // Block represents a block of code in a source code file 66 | // and includes coverage details. 67 | type Block struct { 68 | StartLine int `json:"start_line"` 69 | StartCol int `json:"start_col"` 70 | EndLine int `json:"end_line"` 71 | EndCol int `json:"end_col"` 72 | NumStmt int `json:"num_stmt"` 73 | Count int `json:"count"` 74 | } 75 | 76 | // Report represents a code coverage report. 77 | type Report struct { 78 | Coverage float64 `json:"coverage_percent"` 79 | Changed float64 `json:"coverage_changed"` 80 | Covered int64 `json:"lines_covered"` 81 | Lines int64 `json:"lines_total"` 82 | Files []*File `json:"files"` 83 | } 84 | -------------------------------------------------------------------------------- /cmd_cobertura.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/drone-plugins/drone-coverage/coverage/cobertura" 9 | "github.com/mattn/go-zglob" 10 | "github.com/urfave/cli" 11 | "golang.org/x/tools/cover" 12 | ) 13 | 14 | // CoberturaCmd is the exported command for converting Cobertura files. 15 | var CoberturaCmd = cli.Command{ 16 | Name: "cobertura", 17 | Usage: "parse cobertura files", 18 | Flags: []cli.Flag{}, 19 | Action: func(c *cli.Context) error { 20 | return parseCobertura(c) 21 | }, 22 | } 23 | 24 | func parseCobertura(c *cli.Context) error { 25 | 26 | pattern := c.Args().First() 27 | if pattern == "" { 28 | pattern = "**/coverage.xml" 29 | } 30 | 31 | matches, err := zglob.Glob(pattern) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | parser := cobertura.New() 37 | var profiles []*cover.Profile 38 | for _, match := range matches { 39 | parsed, err := parser.ReadFile(match) 40 | if err != nil { 41 | return err 42 | } 43 | for _, p := range parsed { 44 | profiles = addProfile(profiles, p) 45 | } 46 | } 47 | 48 | // create the coverage payload that gets sent to the 49 | // coverage reporting server. 50 | report := profileToReport(profiles) 51 | 52 | out, err := json.MarshalIndent(report, " ", " ") 53 | if err != nil { 54 | return err 55 | } 56 | fmt.Fprintf(os.Stdout, "%s", out) 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /cmd_gocov.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/drone-plugins/drone-coverage/coverage/gocov" 9 | "github.com/mattn/go-zglob" 10 | "github.com/urfave/cli" 11 | "golang.org/x/tools/cover" 12 | ) 13 | 14 | // GocovCmd is the exported command for converting Go coverage files. 15 | var GocovCmd = cli.Command{ 16 | Name: "gocov", 17 | Usage: "parse gocov files", 18 | Flags: []cli.Flag{}, 19 | Action: func(c *cli.Context) error { 20 | return parseGocov(c) 21 | }, 22 | } 23 | 24 | func parseGocov(c *cli.Context) error { 25 | 26 | pattern := c.Args().First() 27 | if pattern == "" { 28 | pattern = "**/*.out" 29 | } 30 | 31 | matches, err := zglob.Glob(pattern) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | parser := gocov.New() 37 | var profiles []*cover.Profile 38 | for _, match := range matches { 39 | parsed, err := parser.ReadFile(match) 40 | if err != nil { 41 | return err 42 | } 43 | for _, p := range parsed { 44 | profiles = addProfile(profiles, p) 45 | } 46 | } 47 | 48 | // create the coverage payload that gets sent to the 49 | // coverage reporting server. 50 | report := profileToReport(profiles) 51 | 52 | out, err := json.MarshalIndent(report, " ", " ") 53 | if err != nil { 54 | return err 55 | } 56 | fmt.Fprintf(os.Stdout, "%s", out) 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /cmd_lcov.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/drone-plugins/drone-coverage/coverage/lcov" 9 | "github.com/mattn/go-zglob" 10 | "github.com/urfave/cli" 11 | "golang.org/x/tools/cover" 12 | ) 13 | 14 | // LcovCmd is the exported command for converting LCOV files. 15 | var LcovCmd = cli.Command{ 16 | Name: "lcov", 17 | Usage: "parse lcov files", 18 | Flags: []cli.Flag{}, 19 | Action: func(c *cli.Context) error { 20 | return parseLcov(c) 21 | }, 22 | } 23 | 24 | func parseLcov(c *cli.Context) error { 25 | 26 | pattern := c.Args().First() 27 | if pattern == "" { 28 | pattern = "**/lcov.info" 29 | } 30 | 31 | matches, err := zglob.Glob(pattern) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | parser := lcov.New() 37 | var profiles []*cover.Profile 38 | for _, match := range matches { 39 | parsed, err := parser.ReadFile(match) 40 | if err != nil { 41 | return err 42 | } 43 | for _, p := range parsed { 44 | profiles = addProfile(profiles, p) 45 | } 46 | } 47 | 48 | // create the coverage payload that gets sent to the 49 | // coverage reporting server. 50 | report := profileToReport(profiles) 51 | 52 | out, err := json.MarshalIndent(report, " ", " ") 53 | if err != nil { 54 | return err 55 | } 56 | fmt.Fprintf(os.Stdout, "%s", out) 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /cmd_publish.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path" 10 | "strings" 11 | "time" 12 | 13 | "github.com/drone-plugins/drone-coverage/client" 14 | "github.com/drone-plugins/drone-coverage/coverage" 15 | "github.com/mattn/go-zglob" 16 | "github.com/sirupsen/logrus" 17 | "github.com/urfave/cli" 18 | "golang.org/x/tools/cover" 19 | ) 20 | 21 | // PublishCmd is the exported command for publishing coverage files. 22 | var PublishCmd = cli.Command{ 23 | Name: "publish", 24 | Usage: "publish coverage report", 25 | Flags: []cli.Flag{ 26 | cli.StringFlag{ 27 | Name: "repo.fullname", 28 | Usage: "repository full name", 29 | EnvVar: "DRONE_REPO", 30 | }, 31 | cli.StringFlag{ 32 | Name: "build.event", 33 | Value: "push", 34 | Usage: "build event", 35 | EnvVar: "DRONE_BUILD_EVENT", 36 | }, 37 | cli.IntFlag{ 38 | Name: "build.number", 39 | Usage: "build number", 40 | EnvVar: "DRONE_BUILD_NUMBER", 41 | }, 42 | cli.StringFlag{ 43 | Name: "build.link", 44 | Usage: "build link", 45 | EnvVar: "DRONE_BUILD_LINK", 46 | }, 47 | cli.StringFlag{ 48 | Name: "commit.sha", 49 | Usage: "git commit sha", 50 | EnvVar: "DRONE_COMMIT_SHA", 51 | }, 52 | cli.StringFlag{ 53 | Name: "commit.ref", 54 | Value: "refs/heads/master", 55 | Usage: "git commit ref", 56 | EnvVar: "DRONE_COMMIT_REF", 57 | }, 58 | cli.StringFlag{ 59 | Name: "commit.branch", 60 | Value: "master", 61 | Usage: "git commit branch", 62 | EnvVar: "DRONE_COMMIT_BRANCH", 63 | }, 64 | cli.StringFlag{ 65 | Name: "commit.message", 66 | Usage: "git commit message", 67 | EnvVar: "DRONE_COMMIT_MESSAGE", 68 | }, 69 | cli.StringFlag{ 70 | Name: "commit.author.name", 71 | Usage: "git author name", 72 | EnvVar: "DRONE_COMMIT_AUTHOR", 73 | }, 74 | cli.StringFlag{ 75 | Name: "commit.author.avatar", 76 | Usage: "git author avatar", 77 | EnvVar: "DRONE_COMMIT_AUTHOR_AVATAR", 78 | }, 79 | cli.StringFlag{ 80 | Name: "pattern", 81 | Usage: "coverage file pattern", 82 | Value: "**/*.*", 83 | EnvVar: "PLUGIN_PATTERN", 84 | }, 85 | cli.StringFlag{ 86 | Name: "server", 87 | Usage: "coverage server", 88 | EnvVar: "PLUGIN_SERVER", 89 | }, 90 | cli.Float64Flag{ 91 | Name: "threshold", 92 | Usage: "coverage threshold", 93 | EnvVar: "PLUGIN_THRESHOLD", 94 | }, 95 | cli.BoolFlag{ 96 | Name: "increase", 97 | Usage: "coverage must increase", 98 | EnvVar: "PLUGIN_MUST_INCREASE", 99 | }, 100 | cli.StringFlag{ 101 | Name: "cert", 102 | Usage: "coverage cert", 103 | EnvVar: "COVERAGE_CERT", 104 | }, 105 | cli.StringFlag{ 106 | Name: "token", 107 | Usage: "github token", 108 | EnvVar: "PLUGIN_TOKEN,GITHUB_TOKEN", 109 | }, 110 | cli.StringFlag{ 111 | Name: "env-file", 112 | Usage: "source env file", 113 | }, 114 | cli.StringFlag{ 115 | Name: "trim-prefix", 116 | Usage: "trim prefix from coverage files", 117 | EnvVar: "PLUGIN_TRIM_PREFIX", 118 | }, 119 | }, 120 | Action: func(c *cli.Context) error { 121 | return publish(c) 122 | }, 123 | } 124 | 125 | func publish(c *cli.Context) error { 126 | logrus.Debugf("finding coverage files that match %s", c.String("pattern")) 127 | 128 | matches, err := zglob.Glob(c.String("pattern")) 129 | if err != nil { 130 | return err 131 | } 132 | 133 | var profiles []*cover.Profile 134 | for _, match := range matches { 135 | ok, reader := coverage.FromFile(match) 136 | if !ok { 137 | continue 138 | } 139 | 140 | logrus.Debugf("found coverage file %s", match) 141 | 142 | parsed, rerr := reader.ReadFile(match) 143 | if rerr != nil { 144 | return err 145 | } 146 | 147 | for _, p := range parsed { 148 | profiles = addProfile(profiles, p) 149 | } 150 | } 151 | 152 | // create the coverage payload that gets sent to the 153 | // coverage reporting server. 154 | report := profileToReport(profiles) 155 | 156 | build := client.Build{ 157 | Number: c.Int("build.number"), 158 | Event: c.String("build.event"), 159 | Commit: c.String("commit.sha"), 160 | Branch: c.String("commit.branch"), 161 | Ref: c.String("commit.ref"), 162 | Message: c.String("commit.message"), 163 | Author: c.String("commit.author.name"), 164 | Avatar: c.String("commit.author.avatar"), 165 | Link: c.String("build.link"), 166 | Timestamp: time.Now().UTC().Unix(), 167 | } 168 | 169 | // get the base directory 170 | base := c.String("trim.prefix") 171 | 172 | if len(base) == 0 { 173 | base, err = os.Getwd() 174 | 175 | if err != nil { 176 | return err 177 | } 178 | 179 | logrus.Debug("Using current working directory") 180 | } 181 | 182 | logrus.Debugf("Base directory is %s", base) 183 | 184 | findFileReferences(report, base) 185 | 186 | var ( 187 | repo = c.String("repo.fullname") 188 | server = c.String("server") 189 | secret = c.String("token") 190 | cert = c.String("cert") 191 | ) 192 | 193 | cli := newClient(server, cert, "") 194 | token, err := cli.Token(secret) 195 | if err != nil { 196 | return err 197 | } 198 | cli = newClient(server, cert, token.Access) 199 | 200 | // check and see if the repository exists. if not, activate 201 | if _, err := cli.Repo(repo); err != nil { 202 | if _, err := cli.Activate(repo); err != nil { 203 | return err 204 | } 205 | } 206 | 207 | resp, err := cli.Submit(repo, &build, report) 208 | if err != nil { 209 | return err 210 | } 211 | 212 | switch { 213 | case resp.Changed > 0: 214 | fmt.Printf("Code coverage increased %.1f%% to %.1f%%\n", resp.Changed, resp.Coverage) 215 | case resp.Changed < 0: 216 | fmt.Printf("Code coverage dropped %.1f%% to %.1f%%\n", resp.Changed, resp.Coverage) 217 | default: 218 | fmt.Printf("Code coverage unchanged, %.1f%%\n", resp.Coverage) 219 | } 220 | 221 | if c.Float64("threshold") < resp.Coverage && c.Float64("threshold") != 0 { 222 | return fmt.Errorf("Failing build. Coverage threshold may not fall below %.1f%%\n", c.Float64("threshold")) 223 | } 224 | if resp.Changed < 0 && c.Bool("increase") { 225 | return fmt.Errorf("Failing build. Coverage may not decrease") 226 | } 227 | 228 | return nil 229 | } 230 | 231 | func findFileReferences(report *client.Report, base string) { 232 | var files []*client.File 233 | 234 | // normalize the file path based on the working directory 235 | // also ignore any files outside of the directory 236 | for _, file := range report.Files { 237 | fileName := file.FileName 238 | var prefix string 239 | 240 | if path.IsAbs(fileName) { 241 | if !strings.HasPrefix(fileName, base) { 242 | logrus.Warningf("File referenced in coverage not found at %s", fileName) 243 | continue 244 | } 245 | 246 | prefix = base 247 | } else if _, err := os.Stat(fileName); err == nil { 248 | logrus.Debugf("File found at relative path %s", fileName) 249 | 250 | prefix = "" 251 | } else { 252 | var err error 253 | prefix, err = coverage.PathPrefix(fileName, base) 254 | 255 | if err != nil { 256 | // See if file is on disk 257 | logrus.Warningf("File referenced in coverage not found at %s", fileName) 258 | continue 259 | } 260 | 261 | logrus.Debugf("Found common path at %s", prefix) 262 | } 263 | 264 | // Add the file to the report 265 | file.FileName = strings.TrimPrefix(fileName, prefix) 266 | files = append(files, file) 267 | } 268 | 269 | report.Files = files 270 | } 271 | 272 | // profileToReport is a helper function that converts the merged coverage 273 | // report to the Report JSON format expected by the coverage server. 274 | func profileToReport(profiles []*cover.Profile) *client.Report { 275 | report := client.Report{} 276 | report.Files = make([]*client.File, len(profiles), len(profiles)) 277 | 278 | for i, profile := range profiles { 279 | file := client.File{ 280 | Mode: profile.Mode, 281 | FileName: profile.FileName, 282 | } 283 | 284 | file.Blocks = make([]*client.Block, len(profile.Blocks), len(profile.Blocks)) 285 | for ii, block := range profile.Blocks { 286 | file.Blocks[ii] = &client.Block{ 287 | StartLine: block.StartLine, 288 | StartCol: block.StartCol, 289 | EndLine: block.EndLine, 290 | EndCol: block.EndCol, 291 | NumStmt: block.NumStmt, 292 | Count: block.Count, 293 | } 294 | } 295 | 296 | covered, total, percent := percentCovered(profile) 297 | file.Lines = total 298 | file.Covered = covered 299 | file.Coverage = percent 300 | 301 | report.Files[i] = &file 302 | report.Lines += file.Lines 303 | report.Covered += file.Covered 304 | } 305 | if report.Lines != 0 { 306 | report.Coverage = float64(report.Covered) / float64(report.Lines) * float64(100) 307 | } 308 | return &report 309 | } 310 | 311 | // percentCovered is a helper fucntion that calculate the percent 312 | // coverage for coverage profile. 313 | func percentCovered(p *cover.Profile) (int64, int64, float64) { 314 | var total, covered int64 315 | for _, b := range p.Blocks { 316 | total += int64(b.NumStmt) 317 | if b.Count > 0 { 318 | covered += int64(b.NumStmt) 319 | } 320 | } 321 | var percent float64 322 | if total != 0 { 323 | percent = float64(covered) / float64(total) * float64(100) 324 | } 325 | return covered, total, percent 326 | } 327 | 328 | // newClient returns a new coverage server client. 329 | func newClient(server, cert, token string) client.Client { 330 | pool, err := x509.SystemCertPool() 331 | if err != nil { 332 | pool = x509.NewCertPool() 333 | } 334 | conf := &tls.Config{RootCAs: pool} 335 | pem, _ := ioutil.ReadFile(cert) 336 | if len(pem) != 0 { 337 | pool.AppendCertsFromPEM(pem) 338 | } 339 | if len(token) == 0 { 340 | return client.NewClientTLS(server, conf) 341 | } 342 | return client.NewClientTokenTLS(server, token, conf) 343 | } 344 | -------------------------------------------------------------------------------- /cmd_publish_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/drone-plugins/drone-coverage/client" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func createReport(names ...string) *client.Report { 12 | report := &client.Report{} 13 | var files []*client.File 14 | 15 | for _, name := range names { 16 | file := &client.File{ 17 | FileName: name, 18 | } 19 | 20 | files = append(files, file) 21 | } 22 | 23 | report.Files = files 24 | return report 25 | } 26 | 27 | func TestPathModification(t *testing.T) { 28 | logrus.SetLevel(logrus.DebugLevel) 29 | logrus.SetOutput(os.Stderr) 30 | 31 | // test modified 32 | report := createReport("a/b.go") 33 | findFileReferences(report, "/a/") 34 | 35 | fileName := report.Files[0].FileName 36 | if fileName != "b.go" { 37 | t.Errorf("Expected filename to be b.go not %s", fileName) 38 | } 39 | 40 | // test removed 41 | report = createReport("a/b.go", "b/c.go") 42 | findFileReferences(report, "/a/") 43 | 44 | if len(report.Files) != 1 { 45 | t.Errorf("Expected a file to be removed") 46 | } else { 47 | fileName = report.Files[0].FileName 48 | if fileName != "b.go" { 49 | t.Errorf("Expected filename to be b.go not %s", fileName) 50 | } 51 | } 52 | 53 | // test file present 54 | f, err := os.Create(".a.go") 55 | 56 | if err != nil { 57 | t.Errorf("Could not create file for test") 58 | } 59 | defer os.Remove(f.Name()) 60 | 61 | report = createReport(".a.go") 62 | findFileReferences(report, "/a/") 63 | 64 | if len(report.Files) != 1 { 65 | t.Errorf("Expected file to be present") 66 | } else { 67 | fileName = report.Files[0].FileName 68 | if fileName != ".a.go" { 69 | t.Errorf("Expected filename to be .a.go not %s", fileName) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /coverage/cobertura/cobertura.go: -------------------------------------------------------------------------------- 1 | package cobertura 2 | 3 | import ( 4 | "bufio" 5 | "encoding/xml" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | 10 | "github.com/drone-plugins/drone-coverage/coverage" 11 | "golang.org/x/tools/cover" 12 | ) 13 | 14 | func init() { 15 | coverage.Register(` 16 | 18 | 20 | 189 | 190 | 191 | 192 | C:/local/mvn-coverage-example/src/main/java 193 | --source 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | `) 366 | 367 | var sampleJunitCoverageProfile = []*cover.Profile{ 368 | { 369 | FileName: "Main.java", 370 | Mode: "set", 371 | Blocks: []cover.ProfileBlock{ 372 | {Count: 3, StartLine: 10, EndLine: 10, NumStmt: 1}, 373 | {Count: 3, StartLine: 16, EndLine: 16, NumStmt: 1}, 374 | {Count: 3, StartLine: 17, EndLine: 17, NumStmt: 1}, 375 | {Count: 3, StartLine: 18, EndLine: 18, NumStmt: 1}, 376 | {Count: 3, StartLine: 19, EndLine: 19, NumStmt: 1}, 377 | {Count: 3, StartLine: 23, EndLine: 23, NumStmt: 1}, 378 | {Count: 3, StartLine: 25, EndLine: 25, NumStmt: 1}, 379 | {Count: 3, StartLine: 26, EndLine: 26, NumStmt: 1}, 380 | {Count: 3, StartLine: 28, EndLine: 28, NumStmt: 1}, 381 | {Count: 3, StartLine: 29, EndLine: 29, NumStmt: 1}, 382 | {Count: 3, StartLine: 30, EndLine: 30, NumStmt: 1}, 383 | }, 384 | }, 385 | { 386 | FileName: "search/BinarySearch.java", 387 | Mode: "set", 388 | Blocks: []cover.ProfileBlock{ 389 | {Count: 3, StartLine: 12, EndLine: 12, NumStmt: 1}, 390 | {Count: 3, StartLine: 16, EndLine: 16, NumStmt: 1}, 391 | {Count: 12, StartLine: 18, EndLine: 18, NumStmt: 1}, 392 | {Count: 9, StartLine: 20, EndLine: 20, NumStmt: 1}, 393 | {Count: 9, StartLine: 21, EndLine: 21, NumStmt: 1}, 394 | {Count: 9, StartLine: 23, EndLine: 23, NumStmt: 1}, 395 | {Count: 0, StartLine: 24, EndLine: 24, NumStmt: 1}, 396 | {Count: 9, StartLine: 25, EndLine: 25, NumStmt: 1}, 397 | {Count: 6, StartLine: 26, EndLine: 26, NumStmt: 1}, 398 | {Count: 3, StartLine: 28, EndLine: 28, NumStmt: 1}, 399 | {Count: 9, StartLine: 29, EndLine: 29, NumStmt: 1}, 400 | {Count: 3, StartLine: 31, EndLine: 31, NumStmt: 1}, 401 | }, 402 | }, 403 | { 404 | FileName: "search/ISortedArraySearch.java", 405 | Mode: "set", 406 | }, 407 | { 408 | FileName: "search/LinearSearch.java", 409 | Mode: "set", 410 | Blocks: []cover.ProfileBlock{ 411 | {Count: 3, StartLine: 9, EndLine: 9, NumStmt: 1}, 412 | {Count: 9, StartLine: 13, EndLine: 13, NumStmt: 1}, 413 | {Count: 9, StartLine: 15, EndLine: 15, NumStmt: 1}, 414 | {Count: 3, StartLine: 16, EndLine: 16, NumStmt: 1}, 415 | {Count: 6, StartLine: 17, EndLine: 17, NumStmt: 1}, 416 | {Count: 0, StartLine: 19, EndLine: 19, NumStmt: 1}, 417 | {Count: 0, StartLine: 24, EndLine: 24, NumStmt: 1}, 418 | }, 419 | }, 420 | } 421 | -------------------------------------------------------------------------------- /coverage/coverage.go: -------------------------------------------------------------------------------- 1 | package coverage 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "regexp" 9 | 10 | "golang.org/x/tools/cover" 11 | ) 12 | 13 | // the algorithm uses at most sniffLen bytes to make its decision. 14 | const sniffLen = 128 15 | 16 | // the list of registered sniffers. 17 | var sniffers []*sniffer 18 | 19 | type sniffer struct { 20 | reader Reader 21 | sig []byte 22 | } 23 | 24 | // regular expression that can be used to match common filepath patterns 25 | // as a first check to determine if something is a coverage report. 26 | var includes = regexp.MustCompile(".+\\.out|.+clover.xml|.+coverage.json|.+cobertura-coverage.xml|.+lcov.+") 27 | 28 | // Reader reads and parses a coverage file. 29 | type Reader interface { 30 | 31 | // Read reads a coverage report from the bytes. 32 | Read(src []byte) ([]*cover.Profile, error) 33 | 34 | // ReadFile reads a coverage report from the file path. 35 | ReadFile(string) ([]*cover.Profile, error) 36 | 37 | // ReadProfiles reads a coverage report from the io.Reader. 38 | ReadProfiles(io.Reader) ([]*cover.Profile, error) 39 | } 40 | 41 | // Register registers a reader associated with the sniff pattern. 42 | func Register(sig string, r Reader) { 43 | sniffers = append(sniffers, &sniffer{r, []byte(sig)}) 44 | } 45 | 46 | // FromBytes reads the first 512 bytes of slice and returns a coverage 47 | // report reader that is capable of parsing the coverage data. 48 | func FromBytes(data []byte) (bool, Reader) { 49 | if len(data) > sniffLen { 50 | data = data[:sniffLen] 51 | } 52 | firstNonWS := 0 53 | for ; firstNonWS < len(data) && isWS(data[firstNonWS]); firstNonWS++ { 54 | } 55 | if firstNonWS >= len(data) { 56 | return false, nil 57 | } 58 | data = data[firstNonWS:] 59 | for _, sniffer := range sniffers { 60 | if bytes.HasPrefix(data, sniffer.sig) { 61 | return true, sniffer.reader 62 | } 63 | } 64 | return false, nil 65 | } 66 | 67 | // FromFile reads the first 512 bytes of the file and returns a coverage 68 | // report reader that is capable of parsing the coverage file. 69 | func FromFile(path string) (bool, Reader) { 70 | f, err := os.Open(path) 71 | if err != nil { 72 | return false, nil 73 | } 74 | defer f.Close() 75 | r := io.LimitReader(f, sniffLen) 76 | b, err := ioutil.ReadAll(r) 77 | if err != nil { 78 | return false, nil 79 | } 80 | return FromBytes(b) 81 | } 82 | 83 | // IsMatch returns true if the coverage file path matches a regular expression 84 | // of known patterns for coverage files. 85 | func IsMatch(path string) bool { 86 | return includes.MatchString(path) 87 | } 88 | 89 | // helper function returns true if the byte is whitespace 90 | func isWS(b byte) bool { 91 | switch b { 92 | case '\t', '\n', '\x0c', '\r', ' ': 93 | return true 94 | } 95 | return false 96 | } 97 | -------------------------------------------------------------------------------- /coverage/coverage_test.go: -------------------------------------------------------------------------------- 1 | package coverage 2 | 3 | import "testing" 4 | 5 | func TestFromBytes(t *testing.T) { 6 | Register("mode:", nil) 7 | 8 | // test empty string 9 | ok, _ := FromBytes([]byte("")) 10 | if ok { 11 | t.Errorf("Expect bytes to not match a reader") 12 | } 13 | 14 | // test whitespace that equals the pattern length 15 | ok, _ = FromBytes([]byte(" ")) 16 | if ok { 17 | t.Errorf("Expect bytes to not match a reader") 18 | } 19 | 20 | // test whitespace greater than the pattern length 21 | ok, _ = FromBytes([]byte(" ")) 22 | if ok { 23 | t.Errorf("Expect bytes to not match a reader") 24 | } 25 | 26 | // test a matching pattern 27 | ok, _ = FromBytes([]byte("mode:")) 28 | if !ok { 29 | t.Errorf("Expect bytes to match a reader") 30 | } 31 | 32 | // test whitespace prefixing a matching pattern 33 | ok, _ = FromBytes([]byte(" mode:")) 34 | if !ok { 35 | t.Errorf("Expect bytes to match a reader even with whitespace") 36 | } 37 | } 38 | 39 | func TestIsMatch(t *testing.T) { 40 | var tests = map[string]bool{ 41 | "/drone/src/github.com/octocat/hello-world/coverage.out": true, 42 | "/drone/src/github.com/octocat/hello-world/coverage.stdout": false, 43 | "/drone/src/github.com/octocat/hello-world/clover.xml": true, 44 | "/drone/src/github.com/octocat/hello-world/coverage.json": true, 45 | "/drone/src/github.com/octocat/hello-world/cobertura-coverage.xml": true, 46 | "/drone/src/github.com/octocat/hello-world/lcov.info": true, 47 | "/drone/src/github.com/octocat/hello-world/lcov.txt": true, 48 | } 49 | 50 | for path, match := range tests { 51 | if IsMatch(path) != match { 52 | t.Errorf("Expected filepath match %v for %s", match, path) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /coverage/gocov/gocov.go: -------------------------------------------------------------------------------- 1 | package gocov 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | 9 | "github.com/drone-plugins/drone-coverage/coverage" 10 | "golang.org/x/tools/cover" 11 | ) 12 | 13 | func init() { 14 | coverage.Register("mode:", New()) 15 | } 16 | 17 | type reader struct{} 18 | 19 | // New returns a new Reader for reading and parsing a Go coverage report. 20 | func New() coverage.Reader { 21 | return new(reader) 22 | } 23 | 24 | func (r *reader) Read(src []byte) ([]*cover.Profile, error) { 25 | buf := bytes.NewBuffer(src) 26 | return r.ReadProfiles(buf) 27 | } 28 | 29 | func (r *reader) ReadFile(path string) ([]*cover.Profile, error) { 30 | return cover.ParseProfiles(path) 31 | } 32 | 33 | func (r *reader) ReadProfiles(src io.Reader) ([]*cover.Profile, error) { 34 | file, err := ioutil.TempFile(os.TempDir(), "cover_file_") 35 | if err != nil { 36 | return nil, err 37 | } 38 | defer os.Remove(file.Name()) 39 | if _, err := io.Copy(file, src); err != nil { 40 | return nil, err 41 | } 42 | return r.ReadFile(file.Name()) 43 | } 44 | -------------------------------------------------------------------------------- /coverage/gocov/gocov_test.go: -------------------------------------------------------------------------------- 1 | package gocov 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/drone-plugins/drone-coverage/coverage" 8 | "golang.org/x/tools/cover" 9 | ) 10 | 11 | func TestParse(t *testing.T) { 12 | got, err := New().Read(sampleFile) 13 | if err != nil { 14 | t.Errorf("Expected Go coverage profile parsed successfully, got error %s", err) 15 | } 16 | 17 | if !reflect.DeepEqual(got, sampleProfiles) { 18 | t.Errorf("Expected Go coverage profile matches the test fixture") 19 | } 20 | } 21 | 22 | func TestSniff(t *testing.T) { 23 | ok, _ := coverage.FromBytes([]byte("foo:")) 24 | if ok { 25 | t.Errorf("Expect sniffer does not find match") 26 | } 27 | 28 | ok, r := coverage.FromBytes(sampleFile) 29 | if !ok { 30 | t.Errorf("Expect sniffer to find a match") 31 | } 32 | if _, ok := r.(*reader); !ok { 33 | t.Errorf("Expect sniffer to return a reader") 34 | } 35 | } 36 | 37 | var sampleProfiles = []*cover.Profile{ 38 | { 39 | FileName: "github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go", 40 | Mode: "set", 41 | Blocks: []cover.ProfileBlock{ 42 | {Count: 1, StartLine: 14, StartCol: 50, EndLine: 17, EndCol: 2, NumStmt: 2}, 43 | {Count: 1, StartLine: 20, StartCol: 52, EndLine: 26, EndCol: 15, NumStmt: 5}, 44 | {Count: 1, StartLine: 26, StartCol: 15, EndLine: 29, EndCol: 10, NumStmt: 2}, 45 | {Count: 1, StartLine: 30, StartCol: 3, EndLine: 34, EndCol: 40, NumStmt: 4}, 46 | {Count: 1, StartLine: 35, StartCol: 3, EndLine: 50, EndCol: 50, NumStmt: 11}, 47 | {Count: 1, StartLine: 51, StartCol: 3, EndLine: 52, EndCol: 12, NumStmt: 1}, 48 | {Count: 1, StartLine: 56, StartCol: 2, EndLine: 56, EndCol: 22, NumStmt: 1}, 49 | }, 50 | }, 51 | } 52 | 53 | var sampleFile = []byte(`mode: set 54 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:14.50,17.2 2 1 55 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:20.52,26.15 5 1 56 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:56.2,56.22 1 1 57 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:26.15,29.10 2 1 58 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:30.3,34.40 4 1 59 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:35.3,50.50 11 1 60 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:51.3,52.12 1 1`) 61 | -------------------------------------------------------------------------------- /coverage/jacoco/jacoco.go: -------------------------------------------------------------------------------- 1 | package jacoco 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/drone-plugins/drone-coverage/coverage" 7 | "golang.org/x/tools/cover" 8 | ) 9 | 10 | func init() { 11 | coverage.Register(` 12 | 0 { 72 | count = 1 73 | } 74 | 75 | block := cover.ProfileBlock{} 76 | block.NumStmt = 1 77 | block.StartLine = line 78 | block.EndLine = line 79 | block.Count = count 80 | 81 | if len(cols) != 0 && line > 0 { 82 | block.StartCol = 1 83 | block.EndCol = cols[line-1] 84 | } 85 | 86 | profile.Blocks = append(profile.Blocks, block) 87 | default: 88 | continue 89 | } 90 | } 91 | 92 | return profiles, nil 93 | } 94 | 95 | // this is a helper function that is able to calculate the 96 | func calculateCols(profile *cover.Profile) []int { 97 | var lines []int 98 | 99 | f, err := os.Open(profile.FileName) 100 | if err != nil { 101 | return lines 102 | } 103 | defer f.Close() 104 | 105 | scanner := bufio.NewScanner(f) 106 | for scanner.Scan() { 107 | cols := len(scanner.Bytes()) 108 | lines = append(lines, cols) 109 | } 110 | return lines 111 | } 112 | -------------------------------------------------------------------------------- /coverage/lcov/lcov_test.go: -------------------------------------------------------------------------------- 1 | package lcov 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/drone-plugins/drone-coverage/coverage" 8 | "golang.org/x/tools/cover" 9 | ) 10 | 11 | func TestRead(t *testing.T) { 12 | got, err := New().Read(sampleFile) 13 | if err != nil { 14 | t.Errorf("Expected LCOV parsed successfully, got error %s", err) 15 | } 16 | 17 | if !reflect.DeepEqual(got, sampleProfiles) { 18 | t.Errorf("Expected LCOV parsed file equals test fixture") 19 | } 20 | } 21 | 22 | func TestSniff(t *testing.T) { 23 | ok, _ := coverage.FromBytes([]byte("foo:")) 24 | if ok { 25 | t.Errorf("Expect sniffer does not find match") 26 | } 27 | 28 | ok, r := coverage.FromBytes(sampleFile) 29 | if !ok { 30 | t.Errorf("Expect sniffer to find a match") 31 | } 32 | if _, ok := r.(*reader); !ok { 33 | t.Errorf("Expect sniffer to return an LCOV reader") 34 | } 35 | } 36 | 37 | func TestRelativePathsReplacement(t *testing.T) { 38 | got, err := New().Read(sampleFileWithRelativePaths) 39 | if err != nil { 40 | t.Errorf("Expected LCOV parsed successfully, got error %s", err) 41 | } 42 | 43 | if !reflect.DeepEqual(got, sampleProfilesWithAbsolutePaths) { 44 | t.Errorf("Expected LCOV parsed file equals test fixture") 45 | } 46 | } 47 | 48 | var sampleProfilesWithAbsolutePaths = []*cover.Profile{ 49 | { 50 | FileName: "/drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/function_generator.dart", 51 | Mode: "set", 52 | Blocks: []cover.ProfileBlock{ 53 | {NumStmt: 1, StartLine: 30, EndLine: 30, Count: 1}, 54 | }, 55 | }, 56 | } 57 | 58 | var sampleProfiles = []*cover.Profile{ 59 | { 60 | FileName: "/drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/function_generator.dart", 61 | Mode: "set", 62 | Blocks: []cover.ProfileBlock{ 63 | {NumStmt: 1, StartLine: 30, EndLine: 30, Count: 1}, 64 | {NumStmt: 1, StartLine: 31, EndLine: 31, Count: 1}, 65 | {NumStmt: 1, StartLine: 32, EndLine: 32, Count: 1}, 66 | {NumStmt: 1, StartLine: 34, EndLine: 34, Count: 1}, 67 | {NumStmt: 1, StartLine: 42, EndLine: 42, Count: 0}, 68 | {NumStmt: 1, StartLine: 45, EndLine: 45, Count: 1}, 69 | {NumStmt: 1, StartLine: 48, EndLine: 48, Count: 1}, 70 | {NumStmt: 1, StartLine: 52, EndLine: 52, Count: 1}, 71 | {NumStmt: 1, StartLine: 54, EndLine: 54, Count: 1}, 72 | {NumStmt: 1, StartLine: 58, EndLine: 58, Count: 1}, 73 | {NumStmt: 1, StartLine: 62, EndLine: 62, Count: 1}, 74 | }, 75 | }, 76 | { 77 | FileName: "/drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/annotated_metadata_generator.dart", 78 | Mode: "set", 79 | Blocks: []cover.ProfileBlock{ 80 | {NumStmt: 1, StartLine: 33, EndLine: 33, Count: 1}, 81 | {NumStmt: 1, StartLine: 36, EndLine: 36, Count: 1}, 82 | {NumStmt: 1, StartLine: 37, EndLine: 37, Count: 1}, 83 | {NumStmt: 1, StartLine: 38, EndLine: 38, Count: 1}, 84 | }, 85 | }, 86 | } 87 | 88 | var sampleFileWithRelativePaths = []byte(` 89 | SF:./drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/function_generator.dart 90 | DA:30,84 91 | end_of_record 92 | `) 93 | 94 | var sampleFile = []byte(` 95 | SF:/drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/function_generator.dart 96 | DA:30,84 97 | DA:31,28 98 | DA:32,56 99 | DA:34,56 100 | DA:42,0 101 | DA:45,28 102 | DA:48,28 103 | DA:52,12 104 | DA:54,16 105 | DA:58,28 106 | DA:62,16 107 | end_of_record 108 | SF:/drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/annotated_metadata_generator.dart 109 | DA:33,230 110 | DA:36,479 111 | DA:37,236 112 | DA:38,51 113 | end_of_record 114 | `) 115 | -------------------------------------------------------------------------------- /coverage/path.go: -------------------------------------------------------------------------------- 1 | package coverage 2 | 3 | import "fmt" 4 | 5 | // PathPrefix finds the prefix relative to the base with the curr value. 6 | // It will search the base to find the commonality or return an error if there 7 | // is none. 8 | func PathPrefix(curr string, base string) (string, error) { 9 | cBytes := []byte(curr) 10 | bBytes := []byte(base) 11 | count := len(bBytes) 12 | 13 | // Search for the string 14 | for i, _ := range bBytes { 15 | for j, c := range cBytes { 16 | a := i + j 17 | 18 | if a == count { 19 | return curr[:j], nil 20 | } 21 | 22 | if c != bBytes[a] { 23 | break 24 | } 25 | } 26 | } 27 | 28 | return "", fmt.Errorf("Path %s not found in %s", curr, base) 29 | } 30 | -------------------------------------------------------------------------------- /coverage/path_test.go: -------------------------------------------------------------------------------- 1 | package coverage 2 | 3 | import "testing" 4 | 5 | func TestErrors(t *testing.T) { 6 | _, err := PathPrefix("/d/e/f", "/a/b/c") 7 | if err == nil { 8 | t.Errorf("Expect error with two different abolute paths") 9 | } 10 | 11 | _, err = PathPrefix("d/e/f", "/a/b/c") 12 | if err == nil { 13 | t.Errorf("Expect error with a relative path not in absolute") 14 | } 15 | } 16 | 17 | func TestPrefix(t *testing.T) { 18 | abs, err := PathPrefix("/a/b/c/d", "/a/b/c/") 19 | if err != nil && abs != "/a/b/c/" { 20 | t.Errorf("Expect prefix from two absolute paths %s", abs) 21 | } 22 | 23 | rel, err := PathPrefix("b/c/d/e", "/a/b/c/") 24 | if err != nil && rel != "b/c/" { 25 | t.Errorf("Expect prefix from a relative path %s", rel) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.amd64: -------------------------------------------------------------------------------- 1 | FROM plugins/base:multiarch 2 | 3 | LABEL maintainer="Drone.IO Community " \ 4 | org.label-schema.name="Drone Coverage" \ 5 | org.label-schema.vendor="Drone.IO Community" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | ADD release/linux/amd64/drone-coverage /bin/ 9 | ENTRYPOINT ["/bin/drone-coverage"] 10 | CMD ["publish"] 11 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.arm: -------------------------------------------------------------------------------- 1 | FROM plugins/base:multiarch 2 | 3 | LABEL maintainer="Drone.IO Community " \ 4 | org.label-schema.name="Drone Coverage" \ 5 | org.label-schema.vendor="Drone.IO Community" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | ADD release/linux/arm/drone-coverage /bin/ 9 | ENTRYPOINT ["/bin/drone-coverage"] 10 | CMD ["publish"] 11 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.arm64: -------------------------------------------------------------------------------- 1 | FROM plugins/base:multiarch 2 | 3 | LABEL maintainer="Drone.IO Community " \ 4 | org.label-schema.name="Drone Coverage" \ 5 | org.label-schema.vendor="Drone.IO Community" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | ADD release/linux/arm64/drone-coverage /bin/ 9 | ENTRYPOINT ["/bin/drone-coverage"] 10 | CMD ["publish"] 11 | -------------------------------------------------------------------------------- /docker/Dockerfile.windows.1803: -------------------------------------------------------------------------------- 1 | # escape=` 2 | FROM plugins/base:windows-1803 3 | 4 | LABEL maintainer="Drone.IO Community " ` 5 | org.label-schema.name="Drone Coverage" ` 6 | org.label-schema.vendor="Drone.IO Community" ` 7 | org.label-schema.schema-version="1.0" 8 | 9 | ADD release/windows/amd64/drone-coverage.exe C:/bin/drone-coverage.exe 10 | ENTRYPOINT [ "C:\\bin\\drone-coverage.exe" ] 11 | CMD ["publish"] 12 | -------------------------------------------------------------------------------- /docker/Dockerfile.windows.1809: -------------------------------------------------------------------------------- 1 | # escape=` 2 | FROM plugins/base:windows-1809 3 | 4 | LABEL maintainer="Drone.IO Community " ` 5 | org.label-schema.name="Drone Coverage" ` 6 | org.label-schema.vendor="Drone.IO Community" ` 7 | org.label-schema.schema-version="1.0" 8 | 9 | ADD release/windows/amd64/drone-coverage.exe C:/bin/drone-coverage.exe 10 | ENTRYPOINT [ "C:\\bin\\drone-coverage.exe" ] 11 | CMD ["publish"] 12 | -------------------------------------------------------------------------------- /docker/manifest.tmpl: -------------------------------------------------------------------------------- 1 | image: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} 2 | {{#if build.tags}} 3 | tags: 4 | {{#each build.tags}} 5 | - {{this}} 6 | {{/each}} 7 | {{/if}} 8 | manifests: 9 | - 10 | image: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 11 | platform: 12 | architecture: amd64 13 | os: linux 14 | - 15 | image: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 16 | platform: 17 | architecture: arm64 18 | os: linux 19 | variant: v8 20 | - 21 | image: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm 22 | platform: 23 | architecture: arm 24 | os: linux 25 | variant: v7 26 | - 27 | image: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}windows-1803 28 | platform: 29 | architecture: amd64 30 | os: windows 31 | version: 1803 32 | - 33 | image: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}windows-1809 34 | platform: 35 | architecture: amd64 36 | os: windows 37 | version: 1809 38 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/drone-plugins/drone-coverage 2 | 3 | require ( 4 | github.com/mattn/go-zglob v0.0.1 5 | github.com/sirupsen/logrus v1.3.0 6 | github.com/urfave/cli v1.20.0 7 | golang.org/x/oauth2 v0.0.0-20190130075453-99b60b757ec1 8 | golang.org/x/tools v0.0.0-20190211082357-3744606dbb67 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 5 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 6 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 7 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 8 | github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY= 9 | github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= 13 | github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 14 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 16 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 17 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= 18 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 19 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= 20 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 21 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 22 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= 23 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 24 | golang.org/x/oauth2 v0.0.0-20190111185915-36a7019397c4 h1:Xi5aaGtyrfSB/gXS4Kal2NNpB7uzffL3yzWi2kByI18= 25 | golang.org/x/oauth2 v0.0.0-20190111185915-36a7019397c4/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 26 | golang.org/x/oauth2 v0.0.0-20190123100215-5dab4167f31c/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 27 | golang.org/x/oauth2 v0.0.0-20190130075453-99b60b757ec1 h1:nptVV7hxzzMT+cw8qQ7SoPwc3jCHsGzcOmyy8UKWlFI= 28 | golang.org/x/oauth2 v0.0.0-20190130075453-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 29 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= 30 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 31 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= 32 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 33 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 34 | golang.org/x/tools v0.0.0-20190111214448-fc1d57b08d7b h1:Z5QW7z0ycYrOVRYv3z4FeSZbRNvVwUfXHKQSZKb5A6w= 35 | golang.org/x/tools v0.0.0-20190111214448-fc1d57b08d7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 36 | golang.org/x/tools v0.0.0-20190123130514-9279ec27fd88/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 37 | golang.org/x/tools v0.0.0-20190124014748-78ee07aa9465/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 38 | golang.org/x/tools v0.0.0-20190211082357-3744606dbb67 h1:EPh/O3OZ46PfYCH54ZszS/MiEsjLhwJJkUDIy6gTeJY= 39 | golang.org/x/tools v0.0.0-20190211082357-3744606dbb67/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 40 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 41 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 42 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sirupsen/logrus" 7 | "github.com/urfave/cli" 8 | ) 9 | 10 | var ( 11 | version = "unknown" 12 | ) 13 | 14 | func main() { 15 | app := cli.NewApp() 16 | app.Name = "coverage generator" 17 | app.Usage = "generate and publish coverage reports" 18 | app.Version = version 19 | app.Before = setup 20 | app.Flags = []cli.Flag{ 21 | cli.StringFlag{ 22 | Name: "ci", 23 | Usage: "continuous integration mode", 24 | EnvVar: "CI", 25 | }, 26 | cli.BoolTFlag{ 27 | Name: "debug", 28 | Usage: "debug mode", 29 | EnvVar: "DEBUG", 30 | }, 31 | } 32 | app.Commands = []cli.Command{ 33 | LcovCmd, 34 | GocovCmd, 35 | PublishCmd, 36 | } 37 | 38 | err := app.Run(os.Args) 39 | if err != nil { 40 | logrus.Fatal(err) 41 | } 42 | } 43 | 44 | func setup(c *cli.Context) error { 45 | logrus.SetOutput(os.Stderr) 46 | 47 | if c.GlobalBool("debug") { 48 | logrus.SetLevel(logrus.DebugLevel) 49 | } else { 50 | logrus.SetLevel(logrus.WarnLevel) 51 | } 52 | 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /merge.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sort" 6 | 7 | "golang.org/x/tools/cover" 8 | ) 9 | 10 | func mergeProfiles(p *cover.Profile, merge *cover.Profile) { 11 | if p.Mode != merge.Mode { 12 | log.Fatalf("cannot merge profiles with different modes") 13 | } 14 | // Since the blocks are sorted, we can keep track of where the last block 15 | // was inserted and only look at the blocks after that as targets for merge 16 | startIndex := 0 17 | for _, b := range merge.Blocks { 18 | startIndex = mergeProfileBlock(p, b, startIndex) 19 | } 20 | } 21 | 22 | func mergeProfileBlock(p *cover.Profile, pb cover.ProfileBlock, startIndex int) int { 23 | sortFunc := func(i int) bool { 24 | pi := p.Blocks[i+startIndex] 25 | return pi.StartLine >= pb.StartLine && (pi.StartLine != pb.StartLine || pi.StartCol >= pb.StartCol) 26 | } 27 | 28 | i := 0 29 | if sortFunc(i) != true { 30 | i = sort.Search(len(p.Blocks)-startIndex, sortFunc) 31 | } 32 | i += startIndex 33 | if i < len(p.Blocks) && p.Blocks[i].StartLine == pb.StartLine && p.Blocks[i].StartCol == pb.StartCol { 34 | if p.Blocks[i].EndLine != pb.EndLine || p.Blocks[i].EndCol != pb.EndCol { 35 | log.Fatalf("OVERLAP MERGE: %v %v %v", p.FileName, p.Blocks[i], pb) 36 | } 37 | p.Blocks[i].Count |= pb.Count 38 | } else { 39 | if i > 0 { 40 | pa := p.Blocks[i-1] 41 | if pa.EndLine >= pb.EndLine && (pa.EndLine != pb.EndLine || pa.EndCol > pb.EndCol) { 42 | log.Fatalf("OVERLAP BEFORE: %v %v %v", p.FileName, pa, pb) 43 | } 44 | } 45 | if i < len(p.Blocks)-1 { 46 | pa := p.Blocks[i+1] 47 | if pa.StartLine <= pb.StartLine && (pa.StartLine != pb.StartLine || pa.StartCol < pb.StartCol) { 48 | log.Fatalf("OVERLAP AFTER: %v %v %v", p.FileName, pa, pb) 49 | } 50 | } 51 | p.Blocks = append(p.Blocks, cover.ProfileBlock{}) 52 | copy(p.Blocks[i+1:], p.Blocks[i:]) 53 | p.Blocks[i] = pb 54 | } 55 | return i + 1 56 | } 57 | 58 | func addProfile(profiles []*cover.Profile, p *cover.Profile) []*cover.Profile { 59 | i := sort.Search(len(profiles), func(i int) bool { return profiles[i].FileName >= p.FileName }) 60 | if i < len(profiles) && profiles[i].FileName == p.FileName { 61 | mergeProfiles(profiles[i], p) 62 | } else { 63 | profiles = append(profiles, nil) 64 | copy(profiles[i+1:], profiles[i:]) 65 | profiles[i] = p 66 | } 67 | return profiles 68 | } 69 | -------------------------------------------------------------------------------- /pipeline.libsonnet: -------------------------------------------------------------------------------- 1 | local windows_pipe = '\\\\\\\\.\\\\pipe\\\\docker_engine'; 2 | local windows_pipe_volume = 'docker_pipe'; 3 | local test_pipeline_name = 'testing'; 4 | 5 | local windows(os) = os == 'windows'; 6 | 7 | local golang_image(os, version) = 8 | 'golang:' + '1.11' + if windows(os) then '-windowsservercore-' + version else ''; 9 | 10 | { 11 | test(os='linux', arch='amd64', version=''):: 12 | local is_windows = windows(os); 13 | local golang = golang_image(os, version); 14 | local volumes = if is_windows then [{name: 'gopath', path: 'C:\\\\gopath'}] else [{name: 'gopath', path: '/go',}]; 15 | { 16 | kind: 'pipeline', 17 | name: test_pipeline_name, 18 | platform: { 19 | os: os, 20 | arch: arch, 21 | version: if std.length(version) > 0 then version, 22 | }, 23 | steps: [ 24 | { 25 | name: 'vet', 26 | image: golang, 27 | pull: 'always', 28 | environment: { 29 | GO111MODULE: 'on', 30 | }, 31 | commands: [ 32 | 'go vet ./...', 33 | ], 34 | volumes: volumes, 35 | }, 36 | { 37 | name: 'test', 38 | image: golang, 39 | pull: 'always', 40 | environment: { 41 | GO111MODULE: 'on', 42 | }, 43 | commands: [ 44 | 'go test -cover ./...', 45 | ], 46 | volumes: volumes, 47 | }, 48 | ], 49 | trigger: { 50 | ref: [ 51 | 'refs/heads/master', 52 | 'refs/tags/**', 53 | 'refs/pull/**', 54 | ], 55 | }, 56 | volumes: [{name: 'gopath', temp: {}}] 57 | }, 58 | 59 | build(name, os='linux', arch='amd64', version=''):: 60 | local is_windows = windows(os); 61 | local tag = if is_windows then os + '-' + version else os + '-' + arch; 62 | local file_suffix = std.strReplace(tag, '-', '.'); 63 | local volumes = if is_windows then [{ name: windows_pipe_volume, path: windows_pipe }] else []; 64 | local golang = golang_image(os, version); 65 | local plugin_repo = 'plugins/' + std.splitLimit(name, '-', 1)[1]; 66 | local extension = if is_windows then '.exe' else ''; 67 | { 68 | kind: 'pipeline', 69 | name: tag, 70 | platform: { 71 | os: os, 72 | arch: arch, 73 | version: if std.length(version) > 0 then version, 74 | }, 75 | steps: [ 76 | { 77 | name: 'build-push', 78 | image: golang, 79 | pull: 'always', 80 | environment: { 81 | CGO_ENABLED: '0', 82 | GO111MODULE: 'on', 83 | }, 84 | commands: [ 85 | 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/' + os + '/' + arch + '/' + name + extension, 86 | ], 87 | when: { 88 | event: { 89 | exclude: ['tag'], 90 | }, 91 | }, 92 | }, 93 | { 94 | name: 'build-tag', 95 | image: golang, 96 | pull: 'always', 97 | environment: { 98 | CGO_ENABLED: '0', 99 | GO111MODULE: 'on', 100 | }, 101 | commands: [ 102 | 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/' + os + '/' + arch + '/' + name + extension, 103 | ], 104 | when: { 105 | event: ['tag'], 106 | }, 107 | }, 108 | { 109 | name: 'executable', 110 | image: golang, 111 | pull: 'always', 112 | commands: [ 113 | './release/' + os + '/' + arch + '/' + name + extension + ' --help', 114 | ], 115 | }, 116 | { 117 | name: 'dryrun', 118 | image: 'plugins/docker:' + tag, 119 | pull: 'always', 120 | settings: { 121 | dry_run: true, 122 | tags: tag, 123 | dockerfile: 'docker/Dockerfile.' + file_suffix, 124 | daemon_off: if is_windows then 'true' else 'false', 125 | repo: plugin_repo, 126 | username: { from_secret: 'docker_username' }, 127 | password: { from_secret: 'docker_password' }, 128 | }, 129 | volumes: if std.length(volumes) > 0 then volumes, 130 | when: { 131 | event: ['pull_request'], 132 | }, 133 | }, 134 | { 135 | name: 'publish', 136 | image: 'plugins/docker:' + tag, 137 | pull: 'always', 138 | settings: { 139 | auto_tag: true, 140 | auto_tag_suffix: tag, 141 | daemon_off: if is_windows then 'true' else 'false', 142 | dockerfile: 'docker/Dockerfile.' + file_suffix, 143 | repo: plugin_repo, 144 | username: { from_secret: 'docker_username' }, 145 | password: { from_secret: 'docker_password' }, 146 | }, 147 | volumes: if std.length(volumes) > 0 then volumes, 148 | when: { 149 | event: { 150 | exclude: ['pull_request'], 151 | }, 152 | }, 153 | }, 154 | ], 155 | trigger: { 156 | ref: [ 157 | 'refs/heads/master', 158 | 'refs/tags/**', 159 | 'refs/pull/**', 160 | ], 161 | }, 162 | depends_on: [test_pipeline_name], 163 | volumes: if is_windows then [{ name: windows_pipe_volume, host: { path: windows_pipe } }], 164 | }, 165 | 166 | notifications(os='linux', arch='amd64', version='', depends_on=[]):: 167 | { 168 | kind: 'pipeline', 169 | name: 'notifications', 170 | platform: { 171 | os: os, 172 | arch: arch, 173 | version: if std.length(version) > 0 then version, 174 | }, 175 | steps: [ 176 | { 177 | name: 'manifest', 178 | image: 'plugins/manifest', 179 | pull: 'always', 180 | settings: { 181 | username: { from_secret: 'docker_username' }, 182 | password: { from_secret: 'docker_password' }, 183 | spec: 'docker/manifest.tmpl', 184 | ignore_missing: true, 185 | auto_tag: true, 186 | }, 187 | }, 188 | { 189 | name: 'microbadger', 190 | image: 'plugins/webhook', 191 | pull: 'always', 192 | settings: { 193 | urls: { from_secret: 'microbadger_url' }, 194 | }, 195 | }, 196 | ], 197 | trigger: { 198 | ref: [ 199 | 'refs/heads/master', 200 | 'refs/tags/**', 201 | ], 202 | }, 203 | depends_on: depends_on, 204 | }, 205 | } 206 | --------------------------------------------------------------------------------