From eb405b75d0770349a16969f90042ae680916ff5a Mon Sep 17 00:00:00 2001 From: Michael Edgar Date: Wed, 6 May 2020 09:06:02 -0400 Subject: [PATCH] Support for raw coverage file - Update README with instructions for raw file and Java example --- README.md | 48 ++++++++++++++++++++++++- action.yml | 10 +++++- lib/run.js | 93 ++++++++++++++++++++++++++++++++++++++++-------- src/run.ts | 101 ++++++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 223 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 2a615cdf..723df7e2 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@ The action's step needs to run after your test suite has outputted an LCOV file. | Name | Requirement | Description | | --------------------- | ----------- | ----------- | | `github-token` | _required_ | Must be in form `github-token: ${{ secrets.GITHUB_TOKEN }}`; Coveralls uses this token to verify the posted coverage data on the repo and create a new check based on the results. It is built into Github Actions and does not need to be manually specified in your secrets store. [More Info](https://help.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token)| -| `path-to-lcov` | _optional_ | Default: "./coverage/lcov.info". Local path to the lcov output file produced by your test suite. An error will be thrown if the file can't be found. This is the file that will be sent to the Coveralls API. | +| `path-to-file` | _optional_ | Default: "./coverage/lcov.info". Local path to the coverage output file produced by your test suite. An error will be thrown if the file can't be found. This is the file that will be sent to the Coveralls API. | +| `path-to-lcov` | _optional_ | **Deprecated, use `path-to-file`**. Default: "./coverage/lcov.info". Local path to the lcov output file produced by your test suite. An error will be thrown if the file can't be found. This is the file that will be sent to the Coveralls API. | +| `coverage-format` | _optional_ | The format of your coverage file. Supported values are `lcov` and `raw`. defaults to `lcov`. `raw` formatted file must already contain the coverage information in Coveralls JSON format. | | `flag-name` | _optional (unique required if parallel)_ | Job flag name, e.g. "Unit", "Functional", or "Integration". Will be shown in the Coveralls UI. | | `parallel` | _optional_ | Set to true for parallel (or matrix) based steps, where multiple posts to Coveralls will be performed in the check. `flag-name` needs to be set and unique, e.g. `flag-name: run-${{ matrix.test_number }}` | | `parallel-finished` | _optional_ | Set to true in the last job, after the other parallel jobs steps have completed, this will send a webhook to Coveralls to set the build complete. | @@ -107,6 +109,50 @@ jobs: The "Coveralls Finished" step needs to run after all other steps have completed; it will let Coveralls know that all jobs in the build are done and aggregate coverage calculation can be calculated and notifications sent. +### Java Analysis using coveralls-maven-plugin + +When using the coveralls-maven-plugin, execute the `coveralls:report` Maven goal with the option `-DdryRun=true` to generate a Coveralls JSON file at `./target/coveralls.json`. The Coveralls github-action can then be configured to accept the `raw` file type and the path to the JSON file. + +The following example demonstrates how to test your build with both Java 8 and 11, generating and analysing with Coveralls only for Java 11. + +```yaml +on: ["push", "pull_request"] + +name: Test Coveralls Java and Maven + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [8, 11] + name: Build with JDK ${{matrix.java}} + + steps: + - uses: actions/checkout@v2 + name: Checkout + + - uses: actions/setup-java@v1.3.0 + name: Setup JDK ${{matrix.java}} + with: + java-version: ${{matrix.java}} + + - name: Maven build + run: mvn -B verify package --file pom.xml + + - name: Generate Coveralls File + if: matrix.java == '11' + run: mvn -B coveralls:report -DdryRun=true + + - name: Coveralls Analysis + if: matrix.java == '11' + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-file: ./target/coveralls.json + coverage-format: raw +``` + ## Demo ![demo](https://s3.amazonaws.com/assets.coveralls.io/Coveralls%20Github%20Action%20Demo%20-%20trimmed%20-%204.8x720.gif) diff --git a/action.yml b/action.yml index b4377fee..f7d91adf 100644 --- a/action.yml +++ b/action.yml @@ -5,10 +5,18 @@ author: 'Nick Merwin (Coveralls, Inc.)' inputs: github-token: required: true + path-to-file: + description: 'Path to coverage file' + required: true + default: './coverage/lcov.info' path-to-lcov: - description: 'Path to lcov file' + description: 'Path to lcov file (Deprecated)' required: true default: './coverage/lcov.info' + coverage-format: + description: ' Format of coverage file. Acccepted values are `lcov` and `raw`. Defaults to `lcov`' + required: true + default: 'lcov' parallel: description: 'Set to true if you are running parallel jobs, then use "parallel_finished: true" for the last action.' required: false diff --git a/lib/run.js b/lib/run.js index b6e44f3a..4190a7e3 100644 --- a/lib/run.js +++ b/lib/run.js @@ -1,9 +1,10 @@ "use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; @@ -78,28 +79,92 @@ function run() { return 0; } process.env.COVERALLS_PARALLEL = process.env.COVERALLS_PARALLEL || core.getInput('parallel'); + const defaultPath = './coverage/lcov.info'; + const defaultType = 'lcov'; + const pathToFile = core.getInput('path-to-file'); const pathToLcov = core.getInput('path-to-lcov'); - if (pathToLcov == '') { - throw new Error("No Lcov path specified."); + const formatInput = core.getInput('coverage-format'); + const filetype = formatInput !== defaultType ? formatInput : defaultType; + if (filetype !== defaultType && filetype !== 'raw') { + throw new Error(`Unsupported file format: ${filetype}`); } - console.log(`Using lcov file: ${pathToLcov}`); + // for compatibility with the name path-to-lcov - go through these steps + // 1. use 'path-to-file' if its not the default path + // 2. use 'path-to-lcov' if its not the default + // 3. use 'path-to-file' (which will be the default at this point in time - but i made it more explicit) + const pathToUse = pathToFile !== defaultPath ? pathToFile : ((pathToLcov !== defaultPath) ? pathToLcov : pathToFile); + if (pathToUse == '') { + throw new Error("No file path specified."); + } + console.log(`Using ${filetype} file: ${pathToUse}`); let file; try { - file = fs_1.default.readFileSync(pathToLcov, 'utf8'); + file = fs_1.default.readFileSync(pathToUse, 'utf8'); } catch (err) { - throw new Error("Lcov file not found."); + throw new Error(`${filetype} file not found.`); } const basePath = core.getInput('base-path'); const adjustedFile = basePath ? lcov_processor_1.adjustLcovBasePath(file, basePath) : file; - coveralls.handleInput(adjustedFile, (err, body) => { - if (err) { - core.setFailed(err); - } - else { - core.setOutput('coveralls-api-result', body); - } - }); + if (filetype === 'raw') { + coveralls.getOptions((err, options) => { + if (err) { + core.setFailed(err); + } + const postJson = JSON.parse(adjustedFile); + if (options.flag_name) { + postJson.flag_name = options.flag_name; + } + if (options.git) { + postJson.git = options.git; + } + if (options.run_at) { + postJson.run_at = options.run_at; + } + if (options.service_name) { + postJson.service_name = options.service_name; + } + if (options.service_number) { + postJson.service_number = options.service_number; + } + if (options.service_job_id) { + postJson.service_job_id = options.service_job_id; + } + if (options.service_pull_request) { + postJson.service_pull_request = options.service_pull_request; + } + if (options.repo_token) { + postJson.repo_token = options.repo_token; + } + if (options.parallel) { + postJson.parallel = options.parallel; + } + if (options.flag_name) { + postJson.flag_name = options.flag_name; + } + coveralls.sendToCoveralls(postJson, (err, response, body) => { + if (response.statusCode >= 400) { + core.setFailed(`Bad response: ${response.statusCode} ${body}`); + } + if (err) { + core.setFailed(err); + } + else { + core.setOutput('coveralls-api-result', body); + } + }); + }); + } + else { + coveralls.handleInput(adjustedFile, (err, body) => { + if (err) { + core.setFailed(err); + } + else { + core.setOutput('coveralls-api-result', body); + } + }); + } } catch (error) { core.setFailed(error.message); diff --git a/src/run.ts b/src/run.ts index fe2273fa..bea5c29b 100644 --- a/src/run.ts +++ b/src/run.ts @@ -78,34 +78,109 @@ export async function run() { process.env.COVERALLS_PARALLEL = process.env.COVERALLS_PARALLEL || core.getInput('parallel'); + const defaultPath = './coverage/lcov.info'; + const defaultType = 'lcov'; + + const pathToFile = core.getInput('path-to-file'); const pathToLcov = core.getInput('path-to-lcov'); - if (pathToLcov == '') { - throw new Error("No Lcov path specified."); + const formatInput = core.getInput('coverage-format'); + const filetype = formatInput !== defaultType ? formatInput : defaultType; + + if (filetype !== defaultType && filetype !== 'raw') { + throw new Error(`Unsupported file format: ${filetype}`); } - console.log(`Using lcov file: ${pathToLcov}`); + // for compatibility with the name path-to-lcov - go through these steps + // 1. use 'path-to-file' if its not the default path + // 2. use 'path-to-lcov' if its not the default + // 3. use 'path-to-file' (which will be the default at this point in time - but i made it more explicit) + const pathToUse = pathToFile !== defaultPath ? pathToFile : ((pathToLcov !== defaultPath) ? pathToLcov : pathToFile); + if (pathToUse == '') { + throw new Error("No file path specified."); + } + + console.log(`Using ${filetype} file: ${pathToUse}`); let file; try { - file = fs.readFileSync(pathToLcov, 'utf8'); + file = fs.readFileSync(pathToUse, 'utf8'); } catch (err) { - throw new Error("Lcov file not found."); + throw new Error(`${filetype} file not found.`); } - const basePath = core.getInput('base-path'); const adjustedFile = basePath ? adjustLcovBasePath(file, basePath) : file; - coveralls.handleInput(adjustedFile, (err: string, body: string) => { - if(err){ - core.setFailed(err); - } else { - core.setOutput('coveralls-api-result', body); - } - }); + if (filetype === 'raw') { + coveralls.getOptions((err: string, options: any) => { + if (err) { + core.setFailed(err); + } + + const postJson = JSON.parse(adjustedFile); + + if (options.flag_name) { + postJson.flag_name = options.flag_name; + } + + if (options.git) { + postJson.git = options.git; + } + + if (options.run_at) { + postJson.run_at = options.run_at; + } + + if (options.service_name) { + postJson.service_name = options.service_name; + } + + if (options.service_number) { + postJson.service_number = options.service_number; + } + + if (options.service_job_id) { + postJson.service_job_id = options.service_job_id; + } + + if (options.service_pull_request) { + postJson.service_pull_request = options.service_pull_request; + } + + if (options.repo_token) { + postJson.repo_token = options.repo_token; + } + + if (options.parallel) { + postJson.parallel = options.parallel; + } + if (options.flag_name) { + postJson.flag_name = options.flag_name; + } + coveralls.sendToCoveralls(postJson, (err: string, response: any, body: string) => { + if (response.statusCode >= 400) { + core.setFailed(`Bad response: ${response.statusCode} ${body}`); + } + + if(err){ + core.setFailed(err); + } else { + core.setOutput('coveralls-api-result', body); + } + }); + }); + } else { + coveralls.handleInput(adjustedFile, (err: string, body: string) => { + if(err){ + core.setFailed(err); + } else { + core.setOutput('coveralls-api-result', body); + } + }); + } } catch (error) { core.setFailed(error.message); }