UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

255 lines (218 loc) 6.68 kB
'use strict' const getConfig = require('../../config') const request = require('../requests/request') const id = require('../../id') const log = require('../../log') const { incrementCountMetric, distributionMetric, TELEMETRY_KNOWN_TESTS, TELEMETRY_KNOWN_TESTS_MS, TELEMETRY_KNOWN_TESTS_ERRORS, TELEMETRY_KNOWN_TESTS_RESPONSE_TESTS, TELEMETRY_KNOWN_TESTS_RESPONSE_BYTES, } = require('../../ci-visibility/telemetry') const { getNumFromKnownTests } = require('../../plugins/util/test') const { buildCacheKey, writeToCache, withCache } = require('../requests/fs-cache') const MAX_KNOWN_TESTS_PAGES = 10_000 /** * Deep-merges page tests into aggregate. * Structure: { module: { suite: [testName, ...] } } * * @param {object | null} aggregate * @param {object | null} page * @returns {object | null} */ function mergeKnownTests (aggregate, page) { if (!page) return aggregate if (!aggregate) return page for (const [moduleName, suites] of Object.entries(page)) { if (!suites) continue if (!aggregate[moduleName]) { aggregate[moduleName] = suites continue } for (const [suiteName, tests] of Object.entries(suites)) { if (!tests || tests.length === 0) continue aggregate[moduleName][suiteName] = aggregate[moduleName][suiteName] ? [...aggregate[moduleName][suiteName], ...tests] : tests } } return aggregate } function getKnownTests ({ url, isEvpProxy, evpProxyPrefix, isGzipCompatible, env, service, repositoryUrl, sha, osVersion, osPlatform, osArchitecture, runtimeName, runtimeVersion, custom, }, done) { const cacheKey = buildCacheKey('known-tests', [ sha, service, env, repositoryUrl, osPlatform, osVersion, osArchitecture, runtimeName, runtimeVersion, custom, ]) withCache(cacheKey, (activeCacheKey, cb) => { fetchFromApi({ url, isEvpProxy, evpProxyPrefix, isGzipCompatible, env, service, repositoryUrl, sha, osVersion, osPlatform, osArchitecture, runtimeName, runtimeVersion, custom, cacheKey: activeCacheKey, }, cb) }, done) } /** * Fetches known tests from the API with cursor-based pagination and writes the * result to cache on success. * * @param {object} params * @param {string} params.url * @param {boolean} params.isEvpProxy * @param {string} params.evpProxyPrefix * @param {boolean} params.isGzipCompatible * @param {string} params.env * @param {string} params.service * @param {string} params.repositoryUrl * @param {string} params.sha * @param {string} params.osVersion * @param {string} params.osPlatform * @param {string} params.osArchitecture * @param {string} params.runtimeName * @param {string} params.runtimeVersion * @param {object} [params.custom] * @param {string | null} params.cacheKey * @param {Function} done */ function fetchFromApi ({ url, isEvpProxy, evpProxyPrefix, isGzipCompatible, env, service, repositoryUrl, sha, osVersion, osPlatform, osArchitecture, runtimeName, runtimeVersion, custom, cacheKey, }, done) { const options = { path: '/api/v2/ci/libraries/tests', method: 'POST', headers: { 'Content-Type': 'application/json', }, timeout: 20_000, url, } if (isGzipCompatible) { options.headers['accept-encoding'] = 'gzip' } if (isEvpProxy) { options.path = `${evpProxyPrefix}/api/v2/ci/libraries/tests` options.headers['X-Datadog-EVP-Subdomain'] = 'api' } else { const { apiKey } = getConfig() if (!apiKey) { return done(new Error('Known tests were not fetched because Datadog API key is not defined.')) } options.headers['dd-api-key'] = apiKey } const configurations = { 'os.platform': osPlatform, 'os.version': osVersion, 'os.architecture': osArchitecture, 'runtime.name': runtimeName, 'runtime.version': runtimeVersion, custom, } incrementCountMetric(TELEMETRY_KNOWN_TESTS) const startTime = Date.now() let aggregateTests = null let totalResponseBytes = 0 let pageNumber = 0 function fetchPage (pageState) { pageNumber++ if (pageNumber > MAX_KNOWN_TESTS_PAGES) { log.error('Known tests pagination exceeded maximum of %d pages. Aborting.', MAX_KNOWN_TESTS_PAGES) distributionMetric(TELEMETRY_KNOWN_TESTS_MS, {}, Date.now() - startTime) return done(new Error(`Known tests pagination exceeded maximum of ${MAX_KNOWN_TESTS_PAGES} pages`)) } const pageInfo = pageState ? { page_state: pageState } : {} const data = JSON.stringify({ data: { id: id().toString(10), type: 'ci_app_libraries_tests_request', attributes: { configurations, service, env, repository_url: repositoryUrl, sha, page_info: pageInfo, }, }, }) request(data, options, (err, res, statusCode) => { if (err) { distributionMetric(TELEMETRY_KNOWN_TESTS_MS, {}, Date.now() - startTime) incrementCountMetric(TELEMETRY_KNOWN_TESTS_ERRORS, { statusCode }) return done(err) } try { totalResponseBytes += res.length const { data: { attributes } } = JSON.parse(res) const { tests: pageTests, page_info: responsePageInfo } = attributes aggregateTests = mergeKnownTests(aggregateTests, pageTests) // Check if there are more pages if (responsePageInfo && responsePageInfo.has_next) { if (!responsePageInfo.cursor) { log.error( 'Known tests response has has_next=true but no cursor on page %d. Aborting pagination.', pageNumber ) distributionMetric(TELEMETRY_KNOWN_TESTS_MS, {}, Date.now() - startTime) return done(new Error('Known tests pagination: has_next=true but no cursor')) } return fetchPage(responsePageInfo.cursor) } // Done — no more pages distributionMetric(TELEMETRY_KNOWN_TESTS_MS, {}, Date.now() - startTime) const numTests = getNumFromKnownTests(aggregateTests) distributionMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_TESTS, {}, numTests) distributionMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_BYTES, {}, totalResponseBytes) log.debug('Number of received known tests: %d', numTests) writeToCache(cacheKey, aggregateTests) done(null, aggregateTests) } catch (err) { distributionMetric(TELEMETRY_KNOWN_TESTS_MS, {}, Date.now() - startTime) done(err) } }) } fetchPage(null) } module.exports = { getKnownTests }