Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion docs/format/report.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ stored in [AWS Timestream], please see [Storage Schema].
> All JSON below is simply a pseudo representation of the actual schemas which
> are stored in the [`schemas`](../../schemas) folder.

## LMS build numbers

Report versions 3 and 4 accept both release and CI build numbers. The
prefix has two digits, a dot, two digits, a dot, and one or two digits.
Release builds append a dot and exactly five digits, including leading zeros
(for example, `20.26.9.00001` or `20.26.11.16663`).
CI builds append lowercase `-ci` and an unpadded integer from 1 to 99999
(for example, `20.26.9-ci1` or `20.26.11-ci16663`).
The build number is optional and is preserved unchanged, including the `-ci`
label, when reports are loaded. Legacy report versions 1 and 2 accept only
dotted release build numbers, including when LMS metadata is injected before
upgrading.

## Current

```json
Expand All @@ -30,7 +43,7 @@ stored in [AWS Timestream], please see [Storage Schema].
"sha": "<Git SHA hash representing the commit this was generated for>"
},
"lms": {
"buildNumber": "<LMS build number matching pattern 'XX.XX.XX.XXXXX', optional>",
"buildNumber": "<LMS release or CI build number, optional; see LMS build numbers above>",
"instanceUrl": "<URI of the LMS instance, optional>"
},
"framework": "<Non-empty string matching pattern '^(?!\\s).+(?<!\\s)$>'",
Expand Down
4 changes: 2 additions & 2 deletions schemas/report/v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
"properties": {
"buildNumber": {
"type": "string",
"pattern": "^([0-9]{2}\\.){2}[0-9]{1,2}\\.[0-9]{5}$",
"pattern": "^([0-9]{2}\\.){2}[0-9]{1,2}(\\.[0-9]{5}|-ci[1-9][0-9]{0,4})$",
"errorMessage": {
"pattern": "should be a valid LMS build number (XX.XX.XX.XXXXX)"
"pattern": "should be a valid LMS build number (release: XX.XX.X.XXXXX or XX.XX.XX.XXXXX; CI: XX.XX.X-ciN or XX.XX.XX-ciN, where N is an unpadded integer from 1 to 99999)"
}
},
"instanceUrl": {
Expand Down
4 changes: 2 additions & 2 deletions schemas/report/v4.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
"properties": {
"buildNumber": {
"type": "string",
"pattern": "^([0-9]{2}\\.){2}[0-9]{1,2}\\.[0-9]{5}$",
"pattern": "^([0-9]{2}\\.){2}[0-9]{1,2}(\\.[0-9]{5}|-ci[1-9][0-9]{0,4})$",
"errorMessage": {
"pattern": "should be a valid LMS build number (XX.XX.XX.XXXXX)"
"pattern": "should be a valid LMS build number (release: XX.XX.X.XXXXX or XX.XX.XX.XXXXX; CI: XX.XX.X-ciN or XX.XX.XX-ciN, where N is an unpadded integer from 1 to 99999)"
}
},
"instanceUrl": {
Expand Down
86 changes: 86 additions & 0 deletions test/unit/report.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,46 @@ describe('report', () => {
}, {
name: 'v3',
report: testReportLatestFull
}, {
name: 'v4',
report: testReportV4Full
}];
const releaseBuildNumbers = [
'20.26.9.12345',
'20.26.11.16663',
'20.26.9.00000',
'20.26.11.00001',
'20.26.11.99999'
];
const ciBuildNumbers = [
'20.26.9-ci1',
'20.26.11-ci16663',
'20.26.11-ci99999'
];
const invalidBuildNumbers = [
'20.26.11-ci0',
'20.26.11-ci01',
'20.26.11-ci00001',
'20.26.11-ci100000',
'20.26.11-ci',
'20.26.11-ciabc',
'20.26.11-ci-1',
'20.26.11-ci1.5',
'20.26.11-CI16663',
'20.26.11.ci16663',
'20.26.11ci16663',
'20.26.11-ci.16663',
'2.26.11-ci16663',
'200.26.11-ci16663',
'20.2.11-ci16663',
'20.260.11-ci16663',
'20.26.-ci16663',
'20.26.111-ci16663',
' 20.26.11-ci16663',
'20.26.11-ci16663 ',
'20.26.11.1234',
'20.26.11.123456'
];

for (const { name, report: reportData } of reports) {
it(`adds LMS information to ${name} reports`, () => {
Expand All @@ -439,6 +478,53 @@ describe('report', () => {

expect(report.toJSON().summary.lms).to.deep.equal(lmsInfo);
});

for (const source of ['injected', 'existing']) {
describe(`${name} ${source} LMS build numbers`, () => {
const originalVersion = reportData.reportVersion ?? reportData.version;
const supportsCi = originalVersion >= 3;
const validBuildNumbers = supportsCi ? [...releaseBuildNumbers, ...ciBuildNumbers] : releaseBuildNumbers;
const rejectedBuildNumbers = supportsCi ? invalidBuildNumbers : [...invalidBuildNumbers, ...ciBuildNumbers];
const constructReport = (buildNumber) => {
const data = structuredClone(reportData);
const options = source === 'injected' ? { lmsInfo: { buildNumber } } : {};

if (source === 'existing') {
if (originalVersion === 1) {
data.summary.lmsBuildNumber = buildNumber;
} else {
data.summary.lms = { buildNumber };
}
}

mock.method(fs, 'readFileSync', () => JSON.stringify(data));

return new Report(testReportPath, options);
};

for (const buildNumber of validBuildNumbers) {
it(`preserves ${buildNumber}`, () => {
const report = constructReport(buildNumber);

expect(report.getVersionOriginal()).to.equal(originalVersion);
expect(report.getVersion()).to.equal(originalVersion === 4 ? 4 : latestReportVersion);
expect(report.toJSON().summary.lms.buildNumber).to.equal(buildNumber);
});
}

for (const buildNumber of rejectedBuildNumbers) {
it(`rejects ${JSON.stringify(buildNumber)}`, () => {
const field = originalVersion === 1 ? 'lmsBuildNumber' : 'lms.buildNumber';

expect(() => constructReport(buildNumber)).to.throw(
`[report (v${originalVersion})].summary.${field}: should be a valid LMS build number`
).with.property('message').that.includes(
supportsCi ? 'unpadded integer from 1 to 99999' : '(XX.XX.XX.XXXXX)'
);
});
}
});
}
}
});

Expand Down
Loading