├── .gitignore
├── .github
├── CODEOWNERS
├── dependabot.yaml
└── workflows
│ └── test.yaml
├── LICENSE
├── ci
└── fixtures
│ ├── ruby-minitest.xml
│ ├── subfolder
│ └── python-unittest.xml
│ └── go-gotestsum.xml
├── check-junit-upload.js
├── action.yaml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @DataDog/ci-app-backend @DataDog/ci-app-libraries
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: /
5 | schedule:
6 | interval: weekly
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Datadog, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ci/fixtures/ruby-minitest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Failure:
8 | test_that_it_has_a_version_number(Minitest::Result) [/Home/github.com/DataDog/minitest/minitest-hello/test/minitest/hello_test.rb:5]:
9 | NameError: uninitialized constant Minitest::hello
10 | /Users/John/go/src/github.com/DataDog/minitest/minitest-hello/test/minitest/hello_test.rb:5:in `test_that_it_has_a_version_number'
11 |
12 |
13 |
14 |
15 | Failure:
16 | test_it_does_something_useful(Minitest::Result) [/Home/github.com/DataDog/minitest/minitest-hello/test/minitest/hello_test.rb:9]:
17 | Expected false to be truthy.
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/ci/fixtures/subfolder/python-unittest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Some output
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/check-junit-upload.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { client, v2 } = require("@datadog/datadog-api-client")
4 |
5 | const configuration = client.createConfiguration();
6 | const apiInstance = new v2.CIVisibilityTestsApi(configuration);
7 |
8 | const EXPECTED_NUM_TESTS = 32
9 |
10 | const params = {
11 | filterQuery: `@test.service:${process.env.DD_SERVICE} @git.commit.sha:${process.env.GITHUB_SHA}`,
12 | filterFrom: new Date(new Date().getTime() + -300 * 1000), // Last 5 minutes
13 | filterTo: new Date(),
14 | pageLimit: 50,
15 | };
16 |
17 | const CHECK_INTERVAL_SECONDS = 10
18 | const MAX_NUM_ATTEMPTS = 10
19 |
20 | function getTestData (extraFilter) {
21 | const finalFilterQuery = `${params.filterQuery} ${extraFilter}`
22 | console.log(`🔎 Querying CI Visibility tests with ${finalFilterQuery}.`)
23 | return apiInstance
24 | .listCIAppTestEvents({
25 | ...params,
26 | filterQuery: `${finalFilterQuery}`,
27 | })
28 | .then(data => data.data)
29 | .catch(error => console.error(error))
30 | }
31 |
32 | function waitFor (waitSeconds) {
33 | return new Promise(resolve => setTimeout(() => resolve(), waitSeconds * 1000))
34 | }
35 |
36 | async function checkJunitUpload () {
37 | let numAttempts = 0
38 | let isSuccess = false
39 | let data = []
40 | while (numAttempts++ < MAX_NUM_ATTEMPTS && !isSuccess) {
41 | data = await getTestData(`test_level:test ${process.env.EXTRA_TAGS}`)
42 | if (data.length === EXPECTED_NUM_TESTS) {
43 | isSuccess = true
44 | } else {
45 | const isLastAttempt = numAttempts === MAX_NUM_ATTEMPTS
46 | if (!isLastAttempt) {
47 | console.log(`🔁 Attempt number ${numAttempts} failed, retrying in ${CHECK_INTERVAL_SECONDS} seconds.`)
48 | await waitFor(CHECK_INTERVAL_SECONDS)
49 | }
50 | }
51 | }
52 | if (isSuccess) {
53 | console.log(`✅ Successful check: the API returned ${data.length} tests.`)
54 | process.exit(0)
55 | } else {
56 | console.log(`❌ Failed check: the API returned ${data.length} tests but ${EXPECTED_NUM_TESTS} were expected.`)
57 | process.exit(1)
58 | }
59 | }
60 |
61 | checkJunitUpload()
--------------------------------------------------------------------------------
/action.yaml:
--------------------------------------------------------------------------------
1 | # Composite action to upload junit test result files to Datadog Test Optimization
2 | name: "Datadog JUnitXML Upload"
3 | description: "Upload JUnitXML reports files to Datadog Test Optimization"
4 | inputs:
5 | api_key:
6 | required: true
7 | description: Datadog API key to use to upload the junit files.
8 | site:
9 | required: false
10 | default: datadoghq.com
11 | description: The Datadog site to upload the files to.
12 | files:
13 | required: true
14 | description: JUnit files to upload.
15 | default: .
16 | auto-discovery:
17 | required: true
18 | description: Do a recursive search and automatic junit files discovery in the folders provided in `files` input (current folder if omitted).
19 | default: 'true'
20 | ignored-paths:
21 | required: false
22 | description: A comma-separated list of paths that are ignored when junit files auto-discovery is done. Glob patterns are supported
23 | concurrency:
24 | required: true
25 | description: Controls the maximum number of concurrent file uploads.
26 | default: "20"
27 | node-version:
28 | required: true
29 | description: The node version used to install datadog-ci
30 | default: "20"
31 | tags:
32 | required: false
33 | description: Datadog tags to associate with the uploaded test results.
34 | service:
35 | required: false
36 | description: Service name to use with the uploaded test results.
37 | env:
38 | required: false
39 | description: Datadog env to use for the tests.
40 | logs:
41 | required: false
42 | description: Set to "true" to enable forwarding content from XML reports as logs.
43 | datadog-ci-version:
44 | required: false
45 | description: The version of the @datadog/datadog-ci package to use. It defaults to the latest release (`latest`).
46 | default: "latest"
47 | extra-args:
48 | default: ""
49 | description: Extra args to be passed to the datadog-ci cli.
50 | required: false
51 | runs:
52 | using: "composite"
53 | steps:
54 | - name: Install node
55 | uses: actions/setup-node@v4
56 | with:
57 | node-version: ${{ inputs.node-version }}
58 |
59 | - name: Upload the JUnit files
60 | shell: bash
61 | run: |
62 | npx @datadog/datadog-ci@${{ inputs.datadog-ci-version}} junit upload \
63 | --max-concurrency ${{ inputs.concurrency }} \
64 | ${{ inputs.logs == 'true' && '--logs' || '' }} \
65 | ${{ inputs.auto-discovery == 'true' && '--auto-discovery' || '' }} \
66 | ${{ inputs.ignored-paths != '' && format('--ignored-paths {0}', inputs.ignored-paths) || '' }} \
67 | ${{ inputs.service != '' && format('--service {0}', inputs.service) || '' }} \
68 | ${{ inputs.extra-args }} \
69 | ${{ inputs.files }}
70 | env:
71 | DD_API_KEY: ${{ inputs.api_key }}
72 | DD_SITE: ${{ inputs.site }}
73 | DD_ENV: ${{ inputs.env }}
74 | DD_TAGS: ${{ inputs.tags }}
75 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: 'Test Action'
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - main
7 | - 'release/*'
8 | schedule:
9 | - cron: '0 0 * * *' # Runs at midnight UTC every day
10 |
11 | jobs:
12 | test:
13 | strategy:
14 | matrix:
15 | version: [14, 16, 18, 20]
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Upload reports using a glob pattern
20 | uses: ./
21 | with:
22 | api_key: ${{secrets.DD_API_KEY_CI_VISIBILITY}}
23 | logs: "true"
24 | files: '**/fixtures/**'
25 | service: junit-upload-github-action-tests
26 | env: ci
27 | tags: "foo:bar,alpha:bravo,test.node.version:${{ matrix.version}}"
28 | node-version: ${{ matrix.version}}
29 | - name: Check that test data can be queried
30 | run: |
31 | npm install @datadog/datadog-api-client
32 | node ./check-junit-upload.js
33 | env:
34 | EXTRA_TAGS: "@foo:bar @alpha:bravo @test.node.version:${{ matrix.version}}"
35 | DD_API_KEY: ${{ secrets.DD_API_KEY_CI_VISIBILITY }}
36 | DD_APP_KEY: ${{ secrets.DD_APP_KEY_CI_VISIBILITY }}
37 | DD_SERVICE: junit-upload-github-action-tests
38 | test-older-datadog-ci-version:
39 | runs-on: ubuntu-latest
40 | steps:
41 | - uses: actions/checkout@v4
42 | - name: Grab second latest version of @datadog/datadog-ci
43 | run: |
44 | SECOND_LATEST_VERSION=$(curl -s "https://api.github.com/repos/datadog/datadog-ci/releases" | jq '[.[] | {tag_name: .tag_name, published_at: .published_at}] | sort_by(.published_at) | reverse | .[:2] | .[1] | .tag_name')
45 | echo "SECOND_LATEST_VERSION=$SECOND_LATEST_VERSION" >> $GITHUB_ENV
46 | - name: Upload reports using a glob pattern
47 | uses: ./
48 | with:
49 | # should still work with api-key as input
50 | api-key: ${{secrets.DD_API_KEY_CI_VISIBILITY}}
51 | logs: "true"
52 | files: '**/fixtures/**'
53 | service: junit-upload-github-action-tests
54 | env: ci
55 | tags: "foo:previous,alpha:previous"
56 | datadog-ci-version: ${{ env.SECOND_LATEST_VERSION }}
57 | - name: Check that test data can be queried
58 | run: |
59 | npm install @datadog/datadog-api-client
60 | node ./check-junit-upload.js
61 | env:
62 | EXTRA_TAGS: "@foo:previous @alpha:previous"
63 | DD_API_KEY: ${{ secrets.DD_API_KEY_CI_VISIBILITY }}
64 | DD_APP_KEY: ${{ secrets.DD_APP_KEY_CI_VISIBILITY }}
65 | DD_SERVICE: junit-upload-github-action-tests
66 |
67 | test-should-complain-about-missing-api-key:
68 | runs-on: ubuntu-latest
69 | steps:
70 | - uses: actions/checkout@v4
71 | - name: Upload reports using a glob pattern
72 | uses: ./
73 | id: test_step
74 | with:
75 | logs: "true"
76 | files: '**/fixtures/**'
77 | service: junit-upload-github-action-tests
78 | env: ci
79 | tags: "foo:bar,alpha:bravo"
80 | continue-on-error: true
81 | - name: Check that previous step failed
82 | if: steps.test_step.outcome == 'success'
83 | run: |
84 | echo "The previous step did not fail as expected"
85 | exit 1
86 |
--------------------------------------------------------------------------------
/ci/fixtures/go-gotestsum.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Datadog JUnitXML Upload Action
2 |
3 | This action downloads the [datadog-ci](https://github.com/DataDog/datadog-ci) and uses it to upload JUnitXML files
4 | to the [Test Optimization product](https://docs.datadoghq.com/tests/).
5 |
6 | This action sets up node and requires node `>=14`. You can configure a specific version of node to use.
7 | Note that if you have set up another version already it will override it.
8 |
9 | ## Usage
10 |
11 | ```yaml
12 | name: Test Code
13 | on: [ push ]
14 | jobs:
15 | test:
16 | steps:
17 | - uses: actions/checkout@v3
18 | - run: make tests
19 | - uses: datadog/junit-upload-github-action@v2
20 | with:
21 | api_key: ${{ secrets.DD_API_KEY }}
22 | ```
23 |
24 | ## Inputs
25 |
26 | The action has the following options:
27 |
28 | | Name | Description | Required | Default |
29 | |----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------|
30 | | `api_key` | Datadog API key to use to upload the junit files. | True | |
31 | | `site` | The Datadog site to upload the files to. | False | `datadoghq.com` |
32 | | `files` | Path to file or folder containing XML files to upload | False | `.` |
33 | | `auto-discovery` | Do a recursive search and automatic XML files discovery in the folders provided in `files` input (current folder if omitted). Search for filenames that match `*junit*.xml`, `*test*.xml`, `*TEST-*.xml`. | False | `true` |
34 | | `ignored-paths` | A comma-separated list of paths that are ignored when junit files auto-discovery is done. Glob patterns are supported. | False | |
35 | | `concurrency` | Controls the maximum number of concurrent file uploads | False | `20` |
36 | | `node-version` | The node version to use to install the datadog-ci. It must be `>=14` | False | `20` |
37 | | `tags` | Optional extra tags to add to the tests formatted as a comma separated list of tags. Example: `foo:bar,data:dog` | False | |
38 | | `service` | Service name to use with the uploaded test results. | False | |
39 | | `env` | Optional environment to add to the tests | False | |
40 | | `logs` | When set to "true" enables forwarding content from the XML reports as Logs. The content inside ``, ``, and `` is collected as logs. Logs from elements inside a `` are automatically connected to the test. | False | |
41 | | `datadog-ci-version` | Optionally pin the @datadog/datadog-ci version. | False | `latest` |
42 | | `extra-args` | Extra args to be passed to the datadog-ci junit upload command. | False | |
43 |
--------------------------------------------------------------------------------