Skip to content

Commit

Permalink
chore: tav versions update script (#3680)
Browse files Browse the repository at this point in the history
  • Loading branch information
david-luna authored Oct 31, 2023
1 parent 6c558d9 commit b31f56a
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 26 deletions.
74 changes: 48 additions & 26 deletions .tav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,27 @@ redis:
# subset (the first, plus then the latest in each major.minor).
ioredis-v2-v4:
name: ioredis
versions: '2.0.0 || 2.0.1 || 2.1.0 || 2.2.0 || 2.3.1 || 2.4.3 || 2.5.0 || 3.0.0 || 3.1.4 || 3.2.2 || >3.2.2 <4'
versions: '2.0.0 || 2.0.1 || 2.1.0 || 2.2.0 || 2.3.1 || 2.4.3 || 2.5.0 || 3.0.0 || 3.1.4 || ^3.2.2'
commands: node test/instrumentation/modules/ioredis.test.js
update-versions:
mode: latest-minors
include: '>=2.0.0 <4'
ioredis-v4-v5:
name: ioredis
versions: '4.0.0 || 4.0.2 || 4.1.0 || 4.2.3 || 4.3.1 || 4.4.0 || 4.5.1 || 4.6.3 || 4.7.0 || 4.8.0 || 4.9.0 || 4.9.5 || 4.10.4 || 4.11.2 || 4.12.2 || 4.13.1 || 4.14.4 || 4.15.1 || 4.16.3 || 4.17.3 || 4.18.0 || 4.19.4 || 4.20.0 || 4.21.0 || 4.22.0 || 4.23.1 || 4.24.6 || 4.25.0 || 4.26.0 || 4.27.11 || ^4.28.0'
versions: '4.0.0 || 4.0.2 || 4.1.0 || 4.2.3 || 4.3.1 || 4.4.0 || 4.5.1 || 4.6.3 || 4.7.0 || 4.8.0 || 4.9.5 || 4.10.4 || 4.11.2 || 4.12.2 || 4.13.1 || 4.14.4 || 4.15.1 || 4.16.3 || 4.17.3 || 4.18.0 || 4.19.4 || 4.20.0 || 4.21.0 || 4.22.0 || 4.23.1 || 4.24.6 || 4.25.0 || 4.26.0 || 4.27.11 || ^4.28.5'
node: '>=6'
commands: node test/instrumentation/modules/ioredis.test.js
update-versions:
mode: latest-minors
include: '>=4.0.0 <5'
ioredis:
name: ioredis
versions: '^5.0.0'
versions: '5.0.0 || 5.0.6 || 5.1.0 || 5.2.6 || ^5.3.2'
node: '>=12.22.0'
commands: node test/instrumentation/modules/ioredis.test.js
update-versions:
mode: latest-minors
include: '>=5.0.0 <6'

pg-old-node:
name: pg
Expand Down Expand Up @@ -138,8 +147,11 @@ pg-new-node:
# Latest mongodb-core release (v3.2.7) was released in 2019. We test a subset
# of the full supported range.
mongodb-core:
versions: '1.2.19 || 1.2.32 || 1.3.21 || 2.0.14 || 2.1.20 || 3.0.11 || 3.1.11 || 3.2.7' # latest minors subset of '>=1.2.19 <4'
versions: '1.2.19 || 1.2.32 || 1.3.21 || 2.0.14 || 2.1.20 || 3.0.11 || 3.1.11 || ^3.2.7'
commands: node test/instrumentation/modules/mongodb-core.test.js
update-versions:
mode: latest-minors
include: '>=1.2.19 <4' # latest minors subset of '>=1.2.19 <4'

mongodb-3:
name: mongodb
Expand Down Expand Up @@ -476,25 +488,32 @@ fastify-v2:
fastify-v3:
name: fastify
# Update versions periodically with "./dev-utils/tav-versions-fastify.sh".
versions: '3.0.0 || 3.6.0 || 3.14.2 || 3.20.1 || 3.24.0 || 3.29.0 || 3.29.5 || >3.29.5 <4' # subset of '>=3 <4'
versions: '3.0.0 || 3.6.0 || 3.14.2 || 3.20.1 || 3.24.0 || 3.29.0 || ^3.29.5'
peerDependencies: '@fastify/formbody@^6.0.1'
node: '>=10'
commands:
- node test/instrumentation/modules/fastify/fastify.test.js
- node test/instrumentation/modules/fastify/async-await.test.js
- node test/instrumentation/modules/fastify/set-framework.test.js
- node test/sanitize-field-names/fastify.test.js
update-versions:
mode: max-5
include: '>=3 <4'
fastify:
name: fastify
# Update versions periodically with "./dev-utils/tav-versions-fastify.sh".
versions: '4.0.0 || 4.2.1 || 4.5.2 || 4.8.1 || 4.10.1 || 4.14.0 || 4.17.0 || >4.17.0' # subset of '>=4 <4.0.1 || >4.0.1 <4.16.0 || >4.16.2'
versions: '4.0.0 || 4.5.0 || 4.9.0 || 4.13.0 || 4.19.1 || 4.23.1 || ^4.24.3'
peerDependencies: '@fastify/formbody@^7'
node: '>=14.6.0'
commands:
- node test/instrumentation/modules/fastify/fastify.test.js
- node test/instrumentation/modules/fastify/async-await.test.js
- node test/instrumentation/modules/fastify/set-framework.test.js
- node test/sanitize-field-names/fastify.test.js
update-versions:
mode: max-5
include: '>=4 <5'
exclude: '4.0.1 || >=4.16.0 <=4.16.2'

finalhandler:
versions: '*'
Expand Down Expand Up @@ -531,45 +550,48 @@ aws-sdk:
# However, @awk-sdk/client-* releases *very* frequently (almost every day) and there
# is no need to test *all* those releases. Instead we statically list a subset
# of versions to test.
# Maintenance note: This should be updated periodically using:
# node ./dev-utils/update-tav-versions.js

'@aws-sdk/client-s3':
# Maintenance note: This should be updated periodically using:
# node ./dev-utils/tav-versions.js @aws-sdk/client-s3 ">=3.15.0 <4"
#
# Test v3.15.0, every N=47 of 241 releases, and current latest.
versions: '3.15.0 || 3.56.0 || 3.159.0 || 3.236.0 || 3.319.0 || 3.400.0 || 3.412.0 || >3.412.0 <4'
versions: '3.15.0 || 3.72.0 || 3.170.0 || 3.264.0 || 3.347.0 || 3.435.0 || ^3.436.0'
commands:
- node test/instrumentation/modules/@aws-sdk/client-s3.test.js
node: '>=14'
# Test v3.15.0, current latest and 5 versions in between.
update-versions:
mode: max-5
include: '>=3.15.0 <4'

'@aws-sdk/client-dynamodb':
# Maintenance note: This should be updated periodically using:
# node ./dev-utils/tav-versions.js @aws-sdk/client-dynamodb ">=3.15.0 <4"
#
# Test v3.15.0, every N=44 of 225 releases, and current latest.
versions: '3.15.0 || 3.55.0 || 3.165.0 || 3.234.0 || 3.312.0 || 3.398.0 || 3.410.0 || >3.410.0 <4'
versions: '3.15.0 || 3.72.0 || 3.180.0 || 3.261.0 || 3.344.0 || 3.435.0 || ^3.436.0'
commands:
- node test/instrumentation/modules/@aws-sdk/client-s3.test.js
node: '>=14'
# Test v3.15.0, current latest and 5 versions in between.
update-versions:
mode: max-5
include: '>=3.15.0 <4'

'@aws-sdk/client-sns':
# Maintenance note: This should be updated periodically using:
# node ./dev-utils/tav-versions.js @aws-sdk/client-sns ">=3.15.0 <4"
#
# Test v3.15.0, every N=40 of 205 releases, and current latest.
versions: '3.15.0 || 3.53.0 || 3.154.0 || 3.218.0 || 3.294.0 || 3.360.0 || 3.370.0 || >3.370.0 <4'
versions: '3.15.0 || 3.67.0 || 3.178.0 || 3.259.0 || 3.337.0 || 3.431.0 || ^3.436.0'
commands:
- node test/instrumentation/modules/@aws-sdk/client-sns.test.js
node: '>=14'
# Test v3.15.0, current latest and 5 versions in between.
update-versions:
mode: max-5
include: '>=3.15.0 <4'

'@aws-sdk/client-sqs':
# Maintenance note: This should be updated periodically using:
# node ./dev-utils/tav-versions.js @aws-sdk/client-sqs ">=3.15.0 <4"
#
# Test v3.15.0, every N=43 of 220 releases, and current latest.
versions: '3.15.0 || 3.55.0 || 3.165.0 || 3.238.0 || 3.319.0 || 3.409.0 || 3.418.0 || >3.418.0 <4'
versions: '3.15.0 || 3.58.0 || 3.171.0 || 3.257.0 || 3.335.0 || 3.429.0 || ^3.436.0'
commands:
- node test/instrumentation/modules/@aws-sdk/client-sqs.test.js
node: '>=14'
# Test v3.15.0, current latest and 5 versions in between.
update-versions:
mode: max-5
include: '>=3.15.0 <4'

# - [email protected] added its diagnostics_channel support.
# - In [email protected] the `request.origin` property was added, which we need
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ See the <<upgrade-to-v4>> guide.
[float]
===== Chores
* Add script to update TAV versions based on a config. ({pull}3680[#3680])
[[release-notes-4.1.0]]
==== 4.1.0 - 2023/10/09
Expand Down
241 changes: 241 additions & 0 deletions dev-utils/update-tav-versions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#!/usr/bin/env node

/*
* Copyright Elasticsearch B.V. and other contributors where applicable.
* Licensed under the BSD 2-Clause License; you may not use this file except in
* compliance with the BSD 2-Clause License.
*/

/** @typedef {import('semver').SemVer} SemVer */
/**
* @typedef {Object} TavConfig
* @property {String} [name]
* @property {String} versions
* @property {String} node
* @property {String[]} commands
* @property {Object} update-versions
* @property {String} update-versions.include
* @property {String} [update-versions.exclude]
*/

'use strict';

// This script scans the `.tav.yml` file placed at the root folder and for each
// entry checks if the `update-versions` property is defined. In that case
// the script will update the versions property according to the `update-versions`
// configuration:
// - include(required): Versions satisfiying this range will be in the update
// - exclude(optional): Versions satisfiying this range won't be in the update
// - mode(required): how we want to pick the versions within the range. These are
// - latest-minors: picks 1st version of the range and all the latest minor versions
// - latest-majors: picks 1st version of the range and all the latest major versions
// - max-(n): picks 1st version, the last version and (n) versions in between them
//
// Usage:
// node dev-utils/update-tav-versions.js

const { execSync } = require('child_process');
const { readFileSync, writeFileSync } = require('fs');
const path = require('path');

const semver = require('semver');
const yaml = require('js-yaml');

const TOP = path.resolve(path.join(__dirname, '..'));
const TAV_PATH = path.join(TOP, '.tav.yml');
const UPDATE_PROP = 'update-versions';
const INCLUDE_REGEXP = /^>=\d+(\.\d+){0,2} <\d+(\.\d+){0,2}$/;

async function main() {
/** @type {Map<string, string[]>} */
const pkgVersMap = new Map(); // versions per package name
/** @type {Map<string, string[]>} */
const tavVersMap = new Map(); // versions per TAV configuration

const tavContent = readFileSync(TAV_PATH, { encoding: 'utf-8' });
/** @type {Record<string, TavConfig>} */
const tavConfig = yaml.load(tavContent);
const tavEntries = Object.entries(tavConfig);

for (const entry of tavEntries) {
const [name, cfg] = entry;

if (!cfg[UPDATE_PROP]) continue;

const { mode, include, exclude } = cfg[UPDATE_PROP];

// Check format before doing fetch
if (mode.startsWith('max-')) {
const num = Number(mode.split('-')[1]);
if (isNaN(num)) {
throw new Error(
`Error: TAV config ${name} invalid "max-n" mode, "n" must be a number (current: ${mode})`,
);
}
}
if (!INCLUDE_REGEXP.test(include)) {
throw new Error(
`Error: TAV config ${name} include property must be in the form ">=SemVer <SemVer" (current: ${include})`,
);
}

const pkgName = cfg.name || name;
let pkgVersions = pkgVersMap.get(pkgName);

if (!pkgVersions) {
console.log(`Fetching versions for ${pkgName}`);
pkgVersions = JSON.parse(
execSync(`npm view ${pkgName} versions -j`, { encoding: 'utf-8' }),
);
pkgVersMap.set(pkgName, pkgVersions);
}

let versions;
const filteredVers = pkgVersions
.filter((v) => semver.satisfies(v, include))
.filter((v) => !exclude || !semver.satisfies(v, exclude))
.map(semver.parse);

console.log(
`Calculating ${mode} for ${name} including ${include} and excluding ${
exclude || 'none'
}`,
);
if (mode === 'latest-minors') {
versions = getLatestMinors(filteredVers);
} else if (mode === 'latest-majors') {
versions = getLatestMajors(filteredVers);
} else if (mode.startsWith('max-')) {
const num = Number(mode.split('-')[1]);
versions = getMax(filteredVers, Number(num));
} else {
throw new Error(
`Error: Version selection mode for TAV config ${name} unknown (${mode})`,
);
}

if (versions.length === 0) {
console.log(
`Info: all versions excluded for TAV config ${name}, please review include/exclude. Skipping`,
);
continue;
}

// Assuming `include` is always in the form ">={Lower_limit} <{Higher_Limit}"
// - append lower version if not present
// - append a caret to the latest version in the list to test any higher version
const firstVers = versions[0];
const lastVers = versions[versions.length - 1];
const [low] = include.split(' ').map(semver.coerce);

if (semver.neq(firstVers, low)) {
versions.unshift(low);
}

versions = versions.map((v) => v.toString());
versions[versions.length - 1] = `^${lastVers.toString()}`;
tavVersMap.set(name, versions);
}

// Now modify the file contents using the string so we do not loose comments
const tavLines = tavContent.split('\n');
let tavToUpdate;

tavLines.forEach((line, idx) => {
const isCfgStart = !/^[\s#]/.test(line) && line.endsWith(':');
const tavName = isCfgStart ? line.replace(/[':]/g, '') : undefined;

if (tavName) {
tavToUpdate = tavVersMap.has(tavName) ? tavName : undefined;
} else if (tavToUpdate && line.startsWith(' versions:')) {
console.log(`Updating versions of ${tavToUpdate}`);
const tavVers = tavVersMap.get(tavToUpdate);
tavLines[idx] = ` versions: '${tavVers.join(' || ')}'`;
}
});

writeFileSync(TAV_PATH, tavLines.join('\n'), { encoding: 'utf-8' });
}

// support functions

/**
* From a given ordered list of versions returns the first, num in between and last. Example
* - input: ['5.0.0', '5.0.1', '5.1.0', '5.2.0', '5.3.0', '5.4.0', '5.5.0', '5.6.0', '5.7.0', '5.8.0', '5.8.1', '5.9.0']
* - input: num = 4
* - output: ['5.0.0', '5.1.0', '5.3.0', '5.5.0', '5.7.0', '5.8.1', '5.9.0']
* first ^^^^^^^^^ 4 version in between ^^^^^^^^^^^^ last
*
* @param {SemVer[]} versions the version list where to extract
* @param {Number} num the number of versions that should be in between
* @returns {SemVer[]}
*/
function getMax(versions, num) {
const modulus = Math.floor((versions.length - 2) / num);

return versions.filter(
(v, idx, arr) => idx % modulus === 0 || idx === arr.length - 1,
);
}

/**
* From a given ordered list of versions returns the latest minors. Example
* - input: ['5.0.0', '5.0.1', '5.1.0', '5.2.0', '5.3.0', '5.4.0', '5.5.0', '5.6.0', '5.7.0', '5.8.0', '5.8.1', '5.9.0']
* - output: ['5.0.1', '5.1.0', '5.2.0', '5.3.0', '5.4.0', '5.5.0', '5.6.0', '5.7.0', '5.8.1', '5.9.0']
*
* @param {SemVer[]} versions the version list where to extract latest minoes
* @returns {SemVer[]}
*/
function getLatestMajors(versions) {
// assuming sorted array
const result = [];

for (const ver of versions) {
const lastVer = result[result.length - 1];

if (!lastVer) {
result.push(ver);
continue;
}

if (lastVer.major === ver.major) {
result.pop();
}
result.push(ver);
}

return result;
}

/**
* From a given ordered list of versions returns the latest minors. Example
* - input: ['5.0.0', '5.0.1', '5.1.0', '5.2.0', '5.3.0', '5.4.0', '5.5.0', '5.6.0', '5.7.0', '5.8.0', '5.8.1', '5.9.0']
* - output: ['5.0.1', '5.1.0', '5.2.0', '5.3.0', '5.4.0', '5.5.0', '5.6.0', '5.7.0', '5.8.1', '5.9.0']
*
* @param {SemVer[]} versions the version list where to extract latest minoes
* @returns {SemVer[]}
*/
function getLatestMinors(versions) {
// assuming sorted array
const result = [];

for (const ver of versions) {
const lastVer = result[result.length - 1];

if (!lastVer) {
result.push(ver);
continue;
}

if (lastVer.major !== ver.major || lastVer.minor !== ver.minor) {
result.push(ver);
} else if (lastVer.compare(ver) < 0) {
result[result.length - 1] = ver;
}
}

return result;
}

// run
main();

0 comments on commit b31f56a

Please sign in to comment.