Skip to content

Commit 19d3769

Browse files
committed
Add tests for semver check
1 parent 65eee0e commit 19d3769

File tree

5 files changed

+234
-14
lines changed

5 files changed

+234
-14
lines changed

experimental/packages/opentelemetry-api-metrics/src/internal/global-utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,4 @@ type OTelGlobalAPI = {
9393
version: string;
9494

9595
metrics?: MeterProvider;
96-
};
96+
};

experimental/packages/opentelemetry-api-metrics/src/internal/semver.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,4 @@ export function _makeCompatibilityCheck(
137137
*
138138
* @param version version of the API requesting an instance of the global API
139139
*/
140-
export const isCompatible = _makeCompatibilityCheck(VERSION);
140+
export const isCompatible = _makeCompatibilityCheck(VERSION);

experimental/packages/opentelemetry-api-metrics/test/api/global.test.ts renamed to experimental/packages/opentelemetry-api-metrics/test/internal/global.test.ts

+54-12
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
*/
1616

1717
import * as assert from 'assert';
18-
import { _global, GLOBAL_METRICS_API_KEY } from '../../src/api/global-utils';
18+
import { getGlobal } from '../../src/internal/global-utils';
19+
import { _globalThis } from '../../src/platform';
1920
import { NoopMeterProvider } from '../../src/NoopMeterProvider';
21+
import sinon = require('sinon');
22+
import { diag } from '@opentelemetry/api';
2023

2124
const api1 = require('../../src') as typeof import('../../src');
2225

@@ -26,18 +29,31 @@ for (const key of Object.keys(require.cache)) {
2629
}
2730
const api2 = require('../../src') as typeof import('../../src');
2831

32+
// This will need to be changed manually on major version changes.
33+
// It is intentionally not autogenerated to ensure the author of the change is aware of what they are doing.
34+
const GLOBAL_METRICS_API_SYMBOL_KEY = 'opentelemetry.js.api.metrics.0';
35+
36+
const getMockLogger = () => ({
37+
verbose: sinon.spy(),
38+
debug: sinon.spy(),
39+
info: sinon.spy(),
40+
warn: sinon.spy(),
41+
error: sinon.spy(),
42+
});
43+
2944
describe('Global Utils', () => {
3045
// prove they are separate instances
31-
assert.notStrictEqual(api1, api2);
46+
assert.notEqual(api1, api2);
3247
// that return separate noop instances to start
3348
assert.notStrictEqual(
3449
api1.metrics.getMeterProvider(),
35-
api2.metrics.getMeterProvider()
50+
api2.metrics.getMeterProvider(),
3651
);
3752

3853
beforeEach(() => {
3954
api1.metrics.disable();
40-
api2.metrics.disable();
55+
// @ts-expect-error we are modifying internals for testing purposes here
56+
delete _globalThis[Symbol.for(GLOBAL_METRICS_API_SYMBOL_KEY)];
4157
});
4258

4359
it('should change the global meter provider', () => {
@@ -57,20 +73,46 @@ describe('Global Utils', () => {
5773
});
5874

5975
it('should disable both if one is disabled', () => {
60-
const original = api1.metrics.getMeterProvider();
76+
const manager = new NoopMeterProvider();
77+
api1.metrics.setGlobalMeterProvider(manager);
6178

62-
api1.metrics.setGlobalMeterProvider(new NoopMeterProvider());
63-
64-
assert.notStrictEqual(original, api1.metrics.getMeterProvider());
79+
assert.strictEqual(manager, api1.metrics.getMeterProvider());
6580
api2.metrics.disable();
66-
assert.strictEqual(original, api1.metrics.getMeterProvider());
81+
assert.notStrictEqual(manager, api1.metrics.getMeterProvider());
6782
});
6883

6984
it('should return the module NoOp implementation if the version is a mismatch', () => {
70-
const original = api1.metrics.getMeterProvider();
85+
const newMeterProvider = new NoopMeterProvider();
86+
api1.metrics.setGlobalMeterProvider(newMeterProvider);
87+
88+
// ensure new meter provider is returned
89+
assert.strictEqual(api1.metrics.getMeterProvider(), newMeterProvider);
90+
91+
const globalInstance = getGlobal('metrics');
92+
assert.ok(globalInstance);
93+
// @ts-expect-error we are modifying internals for testing purposes here
94+
_globalThis[Symbol.for(GLOBAL_METRICS_API_SYMBOL_KEY)].version = '0.0.1';
95+
96+
// ensure new meter provider is not returned because version above is incompatible
97+
assert.notStrictEqual(
98+
api1.metrics.getMeterProvider(),
99+
newMeterProvider
100+
);
101+
});
102+
103+
it('should log an error if there is a duplicate registration', () => {
104+
const logger = getMockLogger();
105+
diag.setLogger(logger);
106+
107+
api1.metrics.setGlobalMeterProvider(new NoopMeterProvider());
71108
api1.metrics.setGlobalMeterProvider(new NoopMeterProvider());
72-
const afterSet = _global[GLOBAL_METRICS_API_KEY]!(-1);
73109

74-
assert.strictEqual(original, afterSet);
110+
sinon.assert.calledOnce(logger.error);
111+
assert.strictEqual(logger.error.firstCall.args.length, 1);
112+
assert.ok(
113+
logger.error.firstCall.args[0].startsWith(
114+
'Error: @opentelemetry/api: Attempted duplicate registration of API: metrics'
115+
)
116+
);
75117
});
76118
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as assert from 'assert';
18+
import {
19+
isCompatible,
20+
_makeCompatibilityCheck,
21+
} from '../../src/internal/semver';
22+
import { VERSION } from '../../src/version';
23+
24+
describe('semver', () => {
25+
it('should be compatible if versions are equal', () => {
26+
assert.ok(isCompatible(VERSION));
27+
});
28+
29+
it('returns false if own version cannot be parsed', () => {
30+
const check = _makeCompatibilityCheck('this is not semver');
31+
assert.ok(!check('1.0.0'));
32+
});
33+
34+
it('incompatible if other version cannot be parsed', () => {
35+
const check = _makeCompatibilityCheck('0.1.2');
36+
assert.ok(!check('this is not semver'));
37+
});
38+
39+
describe('>= 1.0.0', () => {
40+
const globalVersion = '5.5.5';
41+
const vers: [string, boolean][] = [
42+
// same major/minor run should be compatible
43+
['5.5.5', true],
44+
['5.5.6', true],
45+
['5.5.4', true],
46+
47+
// prerelease version should not be compatible
48+
['5.5.5-rc.0', false],
49+
50+
// if our version has a minor version increase, we may try to call methods which don't exist on the global
51+
['5.6.5', false],
52+
['5.6.6', false],
53+
['5.6.4', false],
54+
55+
// if the global version is ahead of us by a minor revision, it has at least the methods we know about
56+
['5.4.5', true],
57+
['5.4.6', true],
58+
['5.4.4', true],
59+
60+
// major version mismatch is always incompatible
61+
['6.5.5', false],
62+
['6.5.6', false],
63+
['6.5.4', false],
64+
['6.6.5', false],
65+
['6.6.6', false],
66+
['6.6.4', false],
67+
['6.4.5', false],
68+
['6.4.6', false],
69+
['6.4.4', false],
70+
['4.5.5', false],
71+
['4.5.6', false],
72+
['4.5.4', false],
73+
['4.6.5', false],
74+
['4.6.6', false],
75+
['4.6.4', false],
76+
['4.4.5', false],
77+
['4.4.6', false],
78+
['4.4.4', false],
79+
];
80+
81+
test(globalVersion, vers);
82+
});
83+
84+
describe('< 1.0.0', () => {
85+
const globalVersion = '0.5.5';
86+
const vers: [string, boolean][] = [
87+
// same minor/patch should be compatible
88+
['0.5.5', true],
89+
90+
// prerelease version should not be compatible
91+
['0.5.5-rc.0', false],
92+
93+
// if our version has a patch version increase, we may try to call methods which don't exist on the global
94+
['0.5.6', false],
95+
96+
// if the global version is ahead of us by a patch revision, it has at least the methods we know about
97+
['0.5.4', true],
98+
99+
// minor version mismatch is always incompatible
100+
['0.6.5', false],
101+
['0.6.6', false],
102+
['0.6.4', false],
103+
['0.4.5', false],
104+
['0.4.6', false],
105+
['0.4.4', false],
106+
107+
// major version mismatch is always incompatible
108+
['1.5.5', false],
109+
['1.5.6', false],
110+
['1.5.4', false],
111+
['1.6.5', false],
112+
['1.6.6', false],
113+
['1.6.4', false],
114+
['1.4.5', false],
115+
['1.4.6', false],
116+
['1.4.4', false],
117+
];
118+
119+
test(globalVersion, vers);
120+
});
121+
122+
describe('global version is prerelease', () => {
123+
const globalVersion = '1.0.0-rc.3';
124+
const vers: [string, boolean][] = [
125+
// must match exactly
126+
['1.0.0', false],
127+
['1.0.0-rc.2', false],
128+
['1.0.0-rc.4', false],
129+
130+
['1.0.0-rc.3', true],
131+
];
132+
133+
test(globalVersion, vers);
134+
});
135+
});
136+
137+
function test(globalVersion: string, vers: [string, boolean][]) {
138+
describe(`global version is ${globalVersion}`, () => {
139+
for (const [version, compatible] of vers) {
140+
it(`API version ${version} ${
141+
compatible ? 'should' : 'should not'
142+
} be able to access global`, () => {
143+
const check = _makeCompatibilityCheck(version);
144+
assert.strictEqual(check(globalVersion), compatible);
145+
});
146+
}
147+
});
148+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as assert from 'assert';
18+
import { VERSION } from '../../src/version';
19+
20+
describe('version', () => {
21+
it('should have generated VERSION.ts', () => {
22+
const pjson = require('../../package.json');
23+
assert.strictEqual(pjson.version, VERSION);
24+
});
25+
26+
it('prerelease tag versions are banned', () => {
27+
// see https://github.com/open-telemetry/opentelemetry-js-api/issues/74
28+
assert.ok(VERSION.match(/^\d+\.\d+\.\d+$/));
29+
});
30+
});

0 commit comments

Comments
 (0)