From 4e170ead24035cd0154b153dcf1e8d69d6ab7ac9 Mon Sep 17 00:00:00 2001 From: Russell Wheatley Date: Fri, 3 Feb 2023 16:57:45 +0000 Subject: [PATCH] feat(perf): Expose modular API that matches the Firebase web JS SDK v9 API (#6771) Co-authored-by: Mike Hardy --- packages/perf/__tests__/perf.test.ts | 8 +- .../perf/UniversalFirebasePerfModule.java | 3 + packages/perf/e2e/HttpMetric.modular.e2e.js | 657 ++++++++++++++++++ packages/perf/e2e/Trace.modular.e2e.js | 610 ++++++++++++++++ packages/perf/e2e/perf.modular.e2e.js | 243 +++++++ packages/perf/ios/RNFBPerf/RNFBPerfModule.m | 11 +- packages/perf/lib/index.d.ts | 26 +- packages/perf/lib/index.js | 48 +- packages/perf/lib/modular/index.js | 89 +++ tests/app.js | 5 +- tests/e2e/globals.js | 5 + 11 files changed, 1693 insertions(+), 12 deletions(-) create mode 100644 packages/perf/e2e/HttpMetric.modular.e2e.js create mode 100644 packages/perf/e2e/Trace.modular.e2e.js create mode 100644 packages/perf/e2e/perf.modular.e2e.js create mode 100644 packages/perf/lib/modular/index.js diff --git a/packages/perf/__tests__/perf.test.ts b/packages/perf/__tests__/perf.test.ts index 26bbed6180..67eebb0e8b 100644 --- a/packages/perf/__tests__/perf.test.ts +++ b/packages/perf/__tests__/perf.test.ts @@ -13,10 +13,10 @@ describe('Performance Monitoring', function () { describe('setPerformanceCollectionEnabled', function () { it('errors if not boolean', function () { - expect(() => { + expect(async () => { // @ts-ignore - perf().setPerformanceCollectionEnabled(); - }).toThrow('must be a boolean'); + await perf().setPerformanceCollectionEnabled(); + }).rejects.toThrow('must be a boolean'); }); }); @@ -109,7 +109,7 @@ describe('Performance Monitoring', function () { it('errors if not boolean', async function () { try { // @ts-ignore - firebase.perf().setPerformanceCollectionEnabled(); + await firebase.perf().setPerformanceCollectionEnabled(); return Promise.reject(new Error('Did not throw')); } catch (e: any) { expect(e.message).toEqual( diff --git a/packages/perf/android/src/main/java/io/invertase/firebase/perf/UniversalFirebasePerfModule.java b/packages/perf/android/src/main/java/io/invertase/firebase/perf/UniversalFirebasePerfModule.java index 14bb2fdd2b..1d1aab1ebb 100644 --- a/packages/perf/android/src/main/java/io/invertase/firebase/perf/UniversalFirebasePerfModule.java +++ b/packages/perf/android/src/main/java/io/invertase/firebase/perf/UniversalFirebasePerfModule.java @@ -55,6 +55,9 @@ public Map getConstants() { constants.put( "isPerformanceCollectionEnabled", FirebasePerformance.getInstance().isPerformanceCollectionEnabled()); + constants.put( + "isInstrumentationEnabled", + true); return constants; } diff --git a/packages/perf/e2e/HttpMetric.modular.e2e.js b/packages/perf/e2e/HttpMetric.modular.e2e.js new file mode 100644 index 0000000000..8ed99aaa60 --- /dev/null +++ b/packages/perf/e2e/HttpMetric.modular.e2e.js @@ -0,0 +1,657 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const aCoolUrl = 'https://invertase.io'; + +describe('HttpMetric modular', function () { + describe('firebase v8 compatibility', function () { + describe('start()', function () { + it('correctly starts with internal flag ', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + await httpMetric.start(); + httpMetric.setHttpResponseCode(200); + should.equal(httpMetric._started, true); + await Utils.sleep(75); + await httpMetric.stop(); + }); + + it('resolves null if already started', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'POST'); + await httpMetric.start(); + should.equal(httpMetric._started, true); + httpMetric.setHttpResponseCode(200); + should.equal(await httpMetric.start(), null); + await Utils.sleep(75); + await httpMetric.stop(); + }); + }); + + describe('stop()', function () { + it('correctly stops with internal flag ', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + await httpMetric.start(); + httpMetric.setHttpResponseCode(500); + httpMetric.setRequestPayloadSize(1337); + httpMetric.setResponseContentType('application/invertase'); + httpMetric.setResponsePayloadSize(1337); + httpMetric.putAttribute('foo', 'bar'); + httpMetric.putAttribute('bar', 'foo'); + await Utils.sleep(100); + await httpMetric.stop(); + should.equal(httpMetric._stopped, true); + }); + + it('resolves null if already stopped', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'POST'); + await httpMetric.start(); + await Utils.sleep(100); + await httpMetric.stop(); + should.equal(await httpMetric.stop(), null); + }); + + it('handles floating point numbers', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'POST'); + await httpMetric.start(); + const floatingPoint = 500.447553; + + httpMetric.setHttpResponseCode(floatingPoint); + httpMetric.setResponsePayloadSize(floatingPoint); + httpMetric.setRequestPayloadSize(floatingPoint); + + await Utils.sleep(100); + await httpMetric.stop(); + }); + }); + + // describe('removeAttribute()', function () { + // it('errors if not a string', async function () { + // const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + // try { + // httpMetric.putAttribute('inver', 'tase'); + // httpMetric.removeAttribute(13377331); + // return Promise.reject(new Error('Did not throw')); + // } catch (e) { + // e.message.should.containEql('must be a string'); + // return Promise.resolve(); + // } + // }); + + // it('removes an attribute', async function () { + // const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + // httpMetric.putAttribute('inver', 'tase'); + // const value = httpMetric.getAttribute('inver'); + // should.equal(value, 'tase'); + // httpMetric.removeAttribute('inver'); + // const value2 = httpMetric.getAttribute('inver'); + // should.equal(value2, null); + // }); + // }); + + describe('getAttribute()', function () { + it('should return null if attribute does not exist', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + const value = httpMetric.getAttribute('inver'); + should.equal(value, null); + }); + + it('should return an attribute string value', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.putAttribute('inver', 'tase'); + const value = httpMetric.getAttribute('inver'); + should.equal(value, 'tase'); + }); + + it('errors if attribute name is not a string', async function () { + try { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.getAttribute(1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + }); + + describe('putAttribute()', function () { + it('sets an attribute string value', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.putAttribute('inver', 'tase'); + const value = httpMetric.getAttribute('inver'); + value.should.equal('tase'); + }); + + it('errors if attribute name is not a string', async function () { + try { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.putAttribute(1337, 'invertase'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + + it('errors if attribute value is not a string', async function () { + try { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.putAttribute('invertase', 1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + + it('errors if attribute name is greater than 40 characters', async function () { + try { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.putAttribute(new Array(41).fill('1').join(''), 1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('a maximum length of 40 characters'); + return Promise.resolve(); + } + }); + + it('errors if attribute value is greater than 100 characters', async function () { + try { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.putAttribute('invertase', new Array(101).fill('1').join('')); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('a maximum length of 100 characters'); + return Promise.resolve(); + } + }); + + it('errors if more than 5 attributes are put', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + + httpMetric.putAttribute('invertase1', '1337'); + httpMetric.putAttribute('invertase2', '1337'); + httpMetric.putAttribute('invertase3', '1337'); + httpMetric.putAttribute('invertase4', '1337'); + httpMetric.putAttribute('invertase5', '1337'); + + try { + httpMetric.putAttribute('invertase6', '1337'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('maximum number of attributes'); + return Promise.resolve(); + } + }); + }); + + // it('getAttributes()', async () => { + // const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + // httpMetric.putAttribute('inver', 'tase'); + // httpMetric.putAttribute('tase', 'baz'); + // const value = httpMetric.getAttributes(); + // JSON.parse(JSON.stringify(value)).should.deepEqual({ + // inver: 'tase', + // tase: 'baz', + // }); + // }); + + describe('setHttpResponseCode()', function () { + it('sets number value', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setHttpResponseCode(500); + should.equal(httpMetric._httpResponseCode, 500); + }); + + it('sets a null value to clear value', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setHttpResponseCode(null); + should.equal(httpMetric._httpResponseCode, null); + }); + + it('errors if not a number or null value', async function () { + try { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setHttpResponseCode('500'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a number or null'); + return Promise.resolve(); + } + }); + }); + + describe('setRequestPayloadSize()', function () { + it('sets number value', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setRequestPayloadSize(13377331); + should.equal(httpMetric._requestPayloadSize, 13377331); + }); + + it('sets a null value to clear value', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setRequestPayloadSize(null); + should.equal(httpMetric._requestPayloadSize, null); + }); + + it('errors if not a number or null value', async function () { + try { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setRequestPayloadSize('1337'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a number or null'); + return Promise.resolve(); + } + }); + }); + + describe('setResponsePayloadSize()', function () { + it('sets number value', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setResponsePayloadSize(13377331); + should.equal(httpMetric._responsePayloadSize, 13377331); + }); + + it('sets a null value to clear value', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setResponsePayloadSize(null); + should.equal(httpMetric._responsePayloadSize, null); + }); + + it('errors if not a number or null value', async function () { + try { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setResponsePayloadSize('1337'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a number or null'); + return Promise.resolve(); + } + }); + }); + + describe('setResponseContentType()', function () { + it('sets string value', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setResponseContentType('application/invertase'); + should.equal(httpMetric._responseContentType, 'application/invertase'); + }); + + it('sets a null value to clear value', async function () { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setResponseContentType(null); + should.equal(httpMetric._responseContentType, null); + }); + + it('errors if not a string or null value', async function () { + try { + const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + httpMetric.setResponseContentType(1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string or null'); + return Promise.resolve(); + } + }); + }); + }); + + describe('modular', function () { + describe('start()', function () { + it('correctly starts with internal flag ', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + await httpMetricRequest.start(); + httpMetricRequest.setHttpResponseCode(200); + should.equal(httpMetricRequest._started, true); + await Utils.sleep(75); + await httpMetricRequest.stop(); + }); + + it('resolves null if already started', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'POST'); + + await httpMetricRequest.start(); + should.equal(httpMetricRequest._started, true); + httpMetricRequest.setHttpResponseCode(200); + should.equal(await httpMetricRequest.start(), null); + await Utils.sleep(75); + await httpMetricRequest.stop(); + }); + }); + + describe('stop()', function () { + it('correctly stops with internal flag ', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + await httpMetricRequest.start(); + httpMetricRequest.setHttpResponseCode(500); + httpMetricRequest.setRequestPayloadSize(1337); + httpMetricRequest.setResponseContentType('application/invertase'); + httpMetricRequest.setResponsePayloadSize(1337); + httpMetricRequest.putAttribute('foo', 'bar'); + httpMetricRequest.putAttribute('bar', 'foo'); + await Utils.sleep(100); + await httpMetricRequest.stop(); + should.equal(httpMetricRequest._stopped, true); + }); + + it('resolves null if already stopped', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'POST'); + await httpMetricRequest.start(); + await Utils.sleep(100); + await httpMetricRequest.stop(); + should.equal(await httpMetricRequest.stop(), null); + }); + + it('handles floating point numbers', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'POST'); + await httpMetricRequest.start(); + const floatingPoint = 500.447553; + + httpMetricRequest.setHttpResponseCode(floatingPoint); + httpMetricRequest.setResponsePayloadSize(floatingPoint); + httpMetricRequest.setRequestPayloadSize(floatingPoint); + + await Utils.sleep(100); + await httpMetricRequest.stop(); + }); + }); + + // describe('removeAttribute()', function () { + // it('errors if not a string', async function () { + // const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + // try { + // httpMetric.putAttribute('inver', 'tase'); + // httpMetric.removeAttribute(13377331); + // return Promise.reject(new Error('Did not throw')); + // } catch (e) { + // e.message.should.containEql('must be a string'); + // return Promise.resolve(); + // } + // }); + + // it('removes an attribute', async function () { + // const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + // httpMetric.putAttribute('inver', 'tase'); + // const value = httpMetric.getAttribute('inver'); + // should.equal(value, 'tase'); + // httpMetric.removeAttribute('inver'); + // const value2 = httpMetric.getAttribute('inver'); + // should.equal(value2, null); + // }); + // }); + + describe('getAttribute()', function () { + it('should return null if attribute does not exist', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + const value = httpMetricRequest.getAttribute('inver'); + should.equal(value, null); + }); + + it('should return an attribute string value', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.putAttribute('inver', 'tase'); + const value = httpMetricRequest.getAttribute('inver'); + should.equal(value, 'tase'); + }); + + it('errors if attribute name is not a string', async function () { + try { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.getAttribute(1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + }); + + describe('putAttribute()', function () { + it('sets an attribute string value', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.putAttribute('inver', 'tase'); + const value = httpMetricRequest.getAttribute('inver'); + value.should.equal('tase'); + }); + + it('errors if attribute name is not a string', async function () { + try { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.putAttribute(1337, 'invertase'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + + it('errors if attribute value is not a string', async function () { + try { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.putAttribute('invertase', 1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + + it('errors if attribute name is greater than 40 characters', async function () { + try { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.putAttribute(new Array(41).fill('1').join(''), 1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('a maximum length of 40 characters'); + return Promise.resolve(); + } + }); + + it('errors if attribute value is greater than 100 characters', async function () { + try { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.putAttribute('invertase', new Array(101).fill('1').join('')); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('a maximum length of 100 characters'); + return Promise.resolve(); + } + }); + + it('errors if more than 5 attributes are put', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + + httpMetricRequest.putAttribute('invertase1', '1337'); + httpMetricRequest.putAttribute('invertase2', '1337'); + httpMetricRequest.putAttribute('invertase3', '1337'); + httpMetricRequest.putAttribute('invertase4', '1337'); + httpMetricRequest.putAttribute('invertase5', '1337'); + + try { + httpMetricRequest.putAttribute('invertase6', '1337'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('maximum number of attributes'); + return Promise.resolve(); + } + }); + }); + + // it('getAttributes()', async () => { + // const httpMetric = firebase.perf().newHttpMetric(aCoolUrl, 'GET'); + // httpMetric.putAttribute('inver', 'tase'); + // httpMetric.putAttribute('tase', 'baz'); + // const value = httpMetric.getAttributes(); + // JSON.parse(JSON.stringify(value)).should.deepEqual({ + // inver: 'tase', + // tase: 'baz', + // }); + // }); + + describe('setHttpResponseCode()', function () { + it('sets number value', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setHttpResponseCode(500); + should.equal(httpMetricRequest._httpResponseCode, 500); + }); + + it('sets a null value to clear value', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setHttpResponseCode(null); + should.equal(httpMetricRequest._httpResponseCode, null); + }); + + it('errors if not a number or null value', async function () { + try { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setHttpResponseCode('500'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a number or null'); + return Promise.resolve(); + } + }); + }); + + describe('setRequestPayloadSize()', function () { + it('sets number value', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setRequestPayloadSize(13377331); + should.equal(httpMetricRequest._requestPayloadSize, 13377331); + }); + + it('sets a null value to clear value', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setRequestPayloadSize(null); + should.equal(httpMetricRequest._requestPayloadSize, null); + }); + + it('errors if not a number or null value', async function () { + try { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setRequestPayloadSize('1337'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a number or null'); + return Promise.resolve(); + } + }); + }); + + describe('setResponsePayloadSize()', function () { + it('sets number value', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setResponsePayloadSize(13377331); + should.equal(httpMetricRequest._responsePayloadSize, 13377331); + }); + + it('sets a null value to clear value', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setResponsePayloadSize(null); + should.equal(httpMetricRequest._responsePayloadSize, null); + }); + + it('errors if not a number or null value', async function () { + try { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setResponsePayloadSize('1337'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a number or null'); + return Promise.resolve(); + } + }); + }); + + describe('setResponseContentType()', function () { + it('sets string value', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setResponseContentType('application/invertase'); + should.equal(httpMetricRequest._responseContentType, 'application/invertase'); + }); + + it('sets a null value to clear value', async function () { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setResponseContentType(null); + should.equal(httpMetricRequest._responseContentType, null); + }); + + it('errors if not a string or null value', async function () { + try { + const { getPerformance, httpMetric } = perfModular; + const perf = getPerformance(); + const httpMetricRequest = httpMetric(perf, aCoolUrl, 'GET'); + httpMetricRequest.setResponseContentType(1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string or null'); + return Promise.resolve(); + } + }); + }); + }); +}); diff --git a/packages/perf/e2e/Trace.modular.e2e.js b/packages/perf/e2e/Trace.modular.e2e.js new file mode 100644 index 0000000000..70b3240314 --- /dev/null +++ b/packages/perf/e2e/Trace.modular.e2e.js @@ -0,0 +1,610 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +describe('Trace modular', function () { + describe('firebase v8 compatibility', function () { + describe('start()', function () { + it('correctly starts with internal flag ', async function () { + const trace = firebase.perf().newTrace('invertase'); + await trace.start(); + should.equal(trace._started, true); + trace.putAttribute('foo', 'bar'); + trace.putMetric('stars', 9001); + await Utils.sleep(125); + await trace.stop(); + }); + it('resolves null if already started', async function () { + const trace = firebase.perf().newTrace('invertase'); + await trace.start(); + should.equal(trace._started, true); + should.equal(await trace.start(), null); + await Utils.sleep(125); + await trace.stop(); + }); + }); + describe('stop()', function () { + it('correctly stops with internal flag ', async function () { + const trace = firebase.perf().newTrace('invertase'); + await trace.start(); + trace.putAttribute('foo', 'bar'); + trace.putAttribute('bar', 'foo'); + trace.putMetric('leet', 1337); + trace.putMetric('chickens', 12); + await Utils.sleep(100); + await trace.stop(); + should.equal(trace._stopped, true); + }); + it('resolves null if already stopped', async function () { + const trace = firebase.perf().newTrace('invertase'); + await trace.start(); + await Utils.sleep(100); + await trace.stop(); + should.equal(await trace.stop(), null); + }); + }); + // describe('removeAttribute()', function () { + // it('errors if not a string', async function () { + // const trace = firebase.perf().newTrace('invertase'); + // try { + // trace.putAttribute('inver', 'tase'); + // trace.removeAttribute(13377331); + // return Promise.reject(new Error('Did not throw')); + // } catch (e) { + // e.message.should.containEql('must be a string'); + // return Promise.resolve(); + // } + // }); + // it('removes an attribute', async function () { + // const trace = firebase.perf().newTrace('invertase'); + // trace.putAttribute('inver', 'tase'); + // const value = trace.getAttribute('inver'); + // should.equal(value, 'tase'); + // trace.removeAttribute('inver'); + // const value2 = trace.getAttribute('inver'); + // should.equal(value2, null); + // }); + // }); + describe('getAttribute()', function () { + it('should return null if attribute does not exist', async function () { + const trace = firebase.perf().newTrace('invertase'); + const value = trace.getAttribute('inver'); + should.equal(value, null); + }); + it('should return an attribute string value', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute('inver', 'tase'); + const value = trace.getAttribute('inver'); + should.equal(value, 'tase'); + }); + it('errors if attribute name is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.getAttribute(1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + }); + describe('putAttribute()', function () { + it('sets an attribute string value', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute('inver', 'tase'); + const value = trace.getAttribute('inver'); + value.should.equal('tase'); + }); + it('errors if attribute name is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute(1337, 'invertase'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + it('errors if attribute value is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute('invertase', 1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + it('errors if attribute name is greater than 40 characters', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute(new Array(41).fill('1').join(''), 1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('a maximum length of 40 characters'); + return Promise.resolve(); + } + }); + it('errors if attribute value is greater than 100 characters', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute('invertase', new Array(101).fill('1').join('')); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('a maximum length of 100 characters'); + return Promise.resolve(); + } + }); + it('errors if more than 5 attributes are put', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute('invertase1', '1337'); + trace.putAttribute('invertase2', '1337'); + trace.putAttribute('invertase3', '1337'); + trace.putAttribute('invertase4', '1337'); + trace.putAttribute('invertase5', '1337'); + try { + trace.putAttribute('invertase6', '1337'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('maximum number of attributes'); + return Promise.resolve(); + } + }); + }); + // it('getAttributes()', function () { + // const trace = firebase.perf().newTrace('invertase'); + // trace.putAttribute('inver', 'tase'); + // trace.putAttribute('tase', 'baz'); + // const value = trace.getAttributes(); + // JSON.parse(JSON.stringify(value)).should.deepEqual({ + // inver: 'tase', + // tase: 'baz', + // }); + // }); + + describe('removeMetric()', function () { + it('errors if name not a string', async function () { + const trace = firebase.perf().newTrace('invertase'); + try { + trace.putMetric('likes', 1337); + trace.removeMetric(13377331); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + it('removes a metric', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 1337); + const value = trace.getMetric('likes'); + should.equal(value, 1337); + trace.removeMetric('likes'); + const value2 = trace.getMetric('likes'); + should.equal(value2, 0); + }); + }); + describe('getMetric()', function () { + it('should return 0 if metric does not exist', async function () { + const trace = firebase.perf().newTrace('invertase'); + const value = trace.getMetric('likes'); + should.equal(value, 0); + }); + it('should return an metric number value', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 7331); + const value = trace.getMetric('likes'); + should.equal(value, 7331); + }); + it('errors if metric name is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.getMetric(1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + }); + describe('putMetric()', function () { + it('sets a metric number value', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 9001); + const value = trace.getMetric('likes'); + value.should.equal(9001); + }); + it('overwrites existing metric if it exists', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 1); + trace.incrementMetric('likes', 9000); + const value = trace.getMetric('likes'); + value.should.equal(9001); + trace.putMetric('likes', 1); + const value2 = trace.getMetric('likes'); + value2.should.equal(1); + }); + it('errors if metric name is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric(1337, 7331); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + it('errors if metric value is not a number', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', '1337'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a number'); + return Promise.resolve(); + } + }); + }); + describe('incrementMetric()', function () { + it('increments a metric number value', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 9000); + trace.incrementMetric('likes', 1); + const value = trace.getMetric('likes'); + value.should.equal(9001); + }); + it('increments a metric even if it does not already exist', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.incrementMetric('likes', 9001); + const value = trace.getMetric('likes'); + value.should.equal(9001); + }); + it('errors if metric name is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.incrementMetric(1337, 1); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + it('errors if incrementBy value is not a number', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.incrementMetric('likes', '1'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a number'); + return Promise.resolve(); + } + }); + }); + it('getMetrics()', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 1337); + trace.putMetric('stars', 6832); + const value = trace.getMetrics(); + JSON.parse(JSON.stringify(value)).should.deepEqual({ + likes: 1337, + stars: 6832, + }); + }); + }); + + describe('modular', function () { + describe('start()', function () { + it('correctly starts with internal flag ', async function () { + const { getPerformance, trace } = perfModular; + const perf = getPerformance(); + const traceInvertase = trace(perf, 'invertase'); + await traceInvertase.start(); + should.equal(traceInvertase._started, true); + traceInvertase.putAttribute('foo', 'bar'); + traceInvertase.putMetric('stars', 9001); + await Utils.sleep(125); + await traceInvertase.stop(); + }); + it('resolves null if already started', async function () { + const { getPerformance, trace } = perfModular; + const perf = getPerformance(); + const traceInvertase = trace(perf, 'invertase'); + await traceInvertase.start(); + should.equal(traceInvertase._started, true); + should.equal(await traceInvertase.start(), null); + await Utils.sleep(125); + await traceInvertase.stop(); + }); + }); + describe('stop()', function () { + it('correctly stops with internal flag ', async function () { + const { getPerformance, trace } = perfModular; + const perf = getPerformance(); + const traceInvertase = trace(perf, 'invertase'); + await traceInvertase.start(); + traceInvertase.putAttribute('foo', 'bar'); + traceInvertase.putAttribute('bar', 'foo'); + traceInvertase.putMetric('leet', 1337); + traceInvertase.putMetric('chickens', 12); + await Utils.sleep(100); + await traceInvertase.stop(); + should.equal(traceInvertase._stopped, true); + }); + it('resolves null if already stopped', async function () { + const { getPerformance, trace } = perfModular; + const perf = getPerformance(); + const traceInvertase = trace(perf, 'invertase'); + await traceInvertase.start(); + await Utils.sleep(100); + await traceInvertase.stop(); + should.equal(await traceInvertase.stop(), null); + }); + }); + // describe('removeAttribute()', function () { + // it('errors if not a string', async function () { + // const { getPerformance, trace } = perfModular; + // const perf = getPerformance(); + // const traceInvertase = trace(perf, 'invertase'); + // try { + // traceInvertase.putAttribute('inver', 'tase'); + // traceInvertase.removeAttribute(13377331); + // return Promise.reject(new Error('Did not throw')); + // } catch (e) { + // e.message.should.containEql('must be a string'); + // return Promise.resolve(); + // } + // }); + // it('removes an attribute', async function () { + // const { getPerformance, trace } = perfModular; + // const perf = getPerformance(); + // const traceInvertase = trace(perf, 'invertase'); + // traceInvertase.putAttribute('inver', 'tase'); + // const value = traceInvertase.getAttribute('inver'); + // should.equal(value, 'tase'); + // traceInvertase.removeAttribute('inver'); + // const value2 = traceInvertase.getAttribute('inver'); + // should.equal(value2, null); + // }); + // }); + describe('getAttribute()', function () { + it('should return null if attribute does not exist', async function () { + const { getPerformance, trace } = perfModular; + const perf = getPerformance(); + const traceInvertase = trace(perf, 'invertase'); + const value = traceInvertase.getAttribute('inver'); + should.equal(value, null); + }); + it('should return an attribute string value', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute('inver', 'tase'); + const value = trace.getAttribute('inver'); + should.equal(value, 'tase'); + }); + it('errors if attribute name is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.getAttribute(1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + }); + describe('putAttribute()', function () { + it('sets an attribute string value', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute('inver', 'tase'); + const value = trace.getAttribute('inver'); + value.should.equal('tase'); + }); + it('errors if attribute name is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute(1337, 'invertase'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + it('errors if attribute value is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute('invertase', 1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + it('errors if attribute name is greater than 40 characters', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute(new Array(41).fill('1').join(''), 1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('a maximum length of 40 characters'); + return Promise.resolve(); + } + }); + it('errors if attribute value is greater than 100 characters', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute('invertase', new Array(101).fill('1').join('')); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('a maximum length of 100 characters'); + return Promise.resolve(); + } + }); + it('errors if more than 5 attributes are put', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putAttribute('invertase1', '1337'); + trace.putAttribute('invertase2', '1337'); + trace.putAttribute('invertase3', '1337'); + trace.putAttribute('invertase4', '1337'); + trace.putAttribute('invertase5', '1337'); + try { + trace.putAttribute('invertase6', '1337'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('maximum number of attributes'); + return Promise.resolve(); + } + }); + }); + // it('getAttributes()', function () { + // const trace = firebase.perf().newTrace('invertase'); + // trace.putAttribute('inver', 'tase'); + // trace.putAttribute('tase', 'baz'); + // const value = trace.getAttributes(); + // JSON.parse(JSON.stringify(value)).should.deepEqual({ + // inver: 'tase', + // tase: 'baz', + // }); + // }); + + describe('removeMetric()', function () { + it('errors if name not a string', async function () { + const trace = firebase.perf().newTrace('invertase'); + try { + trace.putMetric('likes', 1337); + trace.removeMetric(13377331); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + it('removes a metric', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 1337); + const value = trace.getMetric('likes'); + should.equal(value, 1337); + trace.removeMetric('likes'); + const value2 = trace.getMetric('likes'); + should.equal(value2, 0); + }); + }); + describe('getMetric()', function () { + it('should return 0 if metric does not exist', async function () { + const trace = firebase.perf().newTrace('invertase'); + const value = trace.getMetric('likes'); + should.equal(value, 0); + }); + it('should return an metric number value', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 7331); + const value = trace.getMetric('likes'); + should.equal(value, 7331); + }); + it('errors if metric name is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.getMetric(1337); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + }); + describe('putMetric()', function () { + it('sets a metric number value', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 9001); + const value = trace.getMetric('likes'); + value.should.equal(9001); + }); + it('overwrites existing metric if it exists', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 1); + trace.incrementMetric('likes', 9000); + const value = trace.getMetric('likes'); + value.should.equal(9001); + trace.putMetric('likes', 1); + const value2 = trace.getMetric('likes'); + value2.should.equal(1); + }); + it('errors if metric name is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric(1337, 7331); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + it('errors if metric value is not a number', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', '1337'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a number'); + return Promise.resolve(); + } + }); + }); + describe('incrementMetric()', function () { + it('increments a metric number value', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 9000); + trace.incrementMetric('likes', 1); + const value = trace.getMetric('likes'); + value.should.equal(9001); + }); + it('increments a metric even if it does not already exist', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.incrementMetric('likes', 9001); + const value = trace.getMetric('likes'); + value.should.equal(9001); + }); + it('errors if metric name is not a string', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.incrementMetric(1337, 1); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a string'); + return Promise.resolve(); + } + }); + it('errors if incrementBy value is not a number', async function () { + try { + const trace = firebase.perf().newTrace('invertase'); + trace.incrementMetric('likes', '1'); + return Promise.reject(new Error('Did not throw')); + } catch (e) { + e.message.should.containEql('must be a number'); + return Promise.resolve(); + } + }); + }); + it('getMetrics()', async function () { + const trace = firebase.perf().newTrace('invertase'); + trace.putMetric('likes', 1337); + trace.putMetric('stars', 6832); + const value = trace.getMetrics(); + JSON.parse(JSON.stringify(value)).should.deepEqual({ + likes: 1337, + stars: 6832, + }); + }); + }); +}); diff --git a/packages/perf/e2e/perf.modular.e2e.js b/packages/perf/e2e/perf.modular.e2e.js new file mode 100644 index 0000000000..72bbbf14e9 --- /dev/null +++ b/packages/perf/e2e/perf.modular.e2e.js @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +describe('perf() modular', function () { + describe('firebase v8 compatibility', function () { + describe('setPerformanceCollectionEnabled()', function () { + // These depend on `tests/firebase.json` having `perf_auto_collection_enabled` set to false the first time + // The setting is persisted across restarts, reset to false after for local runs where prefs are sticky + afterEach(async function () { + await firebase.perf().setPerformanceCollectionEnabled(false); + }); + + it('true', async function () { + should.equal(firebase.perf().isPerformanceCollectionEnabled, false); + await firebase.perf().setPerformanceCollectionEnabled(true); + should.equal(firebase.perf().isPerformanceCollectionEnabled, true); + }); + + it('false', async function () { + await firebase.perf().setPerformanceCollectionEnabled(false); + should.equal(firebase.perf().isPerformanceCollectionEnabled, false); + }); + }); + + describe('instrumentationEnabled', function () { + afterEach(function () { + const perf = firebase.perf(); + perf.instrumentationEnabled = false; + }); + + it('true', function () { + const perf = firebase.perf(); + + perf.instrumentationEnabled = true; + + should.equal(perf.instrumentationEnabled, true); + }); + + it('should throw Error with wrong parameter', function () { + const perf = firebase.perf(); + + try { + perf.instrumentationEnabled = 'some string'; + + return Promise.reject(new Error('Did not throw Error.')); + } catch (e) { + e.message.should.containEql("'enabled' must be a boolean"); + return Promise.resolve(); + } + }); + }); + + describe('dataCollectionEnabled', function () { + afterEach(function () { + const perf = firebase.perf(); + perf.dataCollectionEnabled = false; + }); + + it('true', function () { + const perf = firebase.perf(); + + perf.dataCollectionEnabled = true; + + should.equal(perf.dataCollectionEnabled, true); + }); + + it('should throw Error with wrong parameter', function () { + const perf = firebase.perf(); + + try { + perf.dataCollectionEnabled = 'some string'; + + return Promise.reject(new Error('Did not throw Error.')); + } catch (e) { + e.message.should.containEql("'enabled' must be a boolean"); + return Promise.resolve(); + } + }); + }); + + describe('startTrace()', function () { + it('resolves a started instance of Trace', async function () { + const trace = await firebase.perf().startTrace('invertase'); + trace.constructor.name.should.be.equal('Trace'); + trace._identifier.should.equal('invertase'); + trace._started.should.equal(true); + await trace.stop(); + }); + }); + + describe('startScreenTrace()', function () { + it('resolves a started instance of a ScreenTrace', async function () { + if (device.getPlatform() === 'android') { + const screenTrace = await firebase.perf().startScreenTrace('FooScreen'); + screenTrace.constructor.name.should.be.equal('ScreenTrace'); + screenTrace._identifier.should.equal('FooScreen'); + await screenTrace.stop(); + screenTrace._stopped.should.equal(true); + } + }); + }); + }); + + describe('modular', function () { + describe('getPerformance', function () { + it('pass app as argument', function () { + const { getPerformance } = perfModular; + + const perf = getPerformance(firebase.app()); + + perf.constructor.name.should.be.equal('FirebasePerfModule'); + }); + + it('no app as argument', function () { + const { getPerformance } = perfModular; + + const perf = getPerformance(); + + perf.constructor.name.should.be.equal('FirebasePerfModule'); + }); + }); + + describe('initializePerformance()', function () { + it('call and set "dataCollectionEnabled" to `false`', async function () { + const { initializePerformance } = perfModular; + + const perf = await initializePerformance(firebase.app(), { dataCollectionEnabled: false }); + + const enabled = perf.dataCollectionEnabled; + + should.equal(enabled, false); + }); + + it('call and set "dataCollectionEnabled" to `true`', async function () { + const { initializePerformance } = perfModular; + + const perf = await initializePerformance(firebase.app(), { dataCollectionEnabled: true }); + + const enabled = perf.dataCollectionEnabled; + + should.equal(enabled, true); + }); + }); + + describe('dataCollectionEnabled', function () { + // These depend on `tests/firebase.json` having `perf_auto_collection_enabled` set to false the first time + // The setting is persisted across restarts, reset to false after for local runs where prefs are sticky + afterEach(async function () { + const { getPerformance } = perfModular; + + const perf = getPerformance(); + perf.dataCollectionEnabled = false; + }); + + it('true', async function () { + const { getPerformance } = perfModular; + + const perf = getPerformance(); + should.equal(perf.dataCollectionEnabled, false); + perf.dataCollectionEnabled = true; + should.equal(perf.dataCollectionEnabled, true); + }); + + it('false', async function () { + const { getPerformance } = perfModular; + + const perf = getPerformance(); + perf.dataCollectionEnabled = false; + should.equal(perf.dataCollectionEnabled, false); + }); + }); + + describe('instrumentationEnabled', function () { + afterEach(function () { + const { getPerformance } = perfModular; + + const perf = getPerformance(); + perf.instrumentationEnabled = false; + }); + + it('true', function () { + const { getPerformance } = perfModular; + + const perf = getPerformance(); + perf.instrumentationEnabled = true; + + should.equal(perf.instrumentationEnabled, true); + }); + + it('false', function () { + if (device.getPlatform() === 'ios') { + // Only possible to change instrumentationEnabled on iOS from the app + const { getPerformance } = perfModular; + + const perf = getPerformance(); + perf.instrumentationEnabled = false; + + should.equal(perf.instrumentationEnabled, false); + } + }); + }); + + describe('startTrace()', function () { + it('resolves a started instance of Trace', async function () { + const { getPerformance, trace } = perfModular; + const perf = getPerformance(); + const traceInvertase = trace(perf, 'invertase'); + await traceInvertase.start(); + traceInvertase.constructor.name.should.be.equal('Trace'); + traceInvertase._identifier.should.equal('invertase'); + traceInvertase._started.should.equal(true); + await traceInvertase.stop(); + }); + }); + + describe('startScreenTrace()', function () { + it('resolves a started instance of a ScreenTrace', async function () { + if (device.getPlatform() === 'android') { + const { getPerformance, startScreenTrace } = perfModular; + const screenTrace = await startScreenTrace(getPerformance(), 'FooScreen'); + screenTrace.constructor.name.should.be.equal('ScreenTrace'); + screenTrace._identifier.should.equal('FooScreen'); + await screenTrace.stop(); + screenTrace._stopped.should.equal(true); + } + }); + }); + }); +}); diff --git a/packages/perf/ios/RNFBPerf/RNFBPerfModule.m b/packages/perf/ios/RNFBPerf/RNFBPerfModule.m index 4200eec877..96570231a3 100644 --- a/packages/perf/ios/RNFBPerf/RNFBPerfModule.m +++ b/packages/perf/ios/RNFBPerf/RNFBPerfModule.m @@ -58,6 +58,8 @@ - (NSDictionary *)constantsToExport { NSMutableDictionary *constants = [NSMutableDictionary new]; constants[@"isPerformanceCollectionEnabled"] = @([RCTConvert BOOL:@([FIRPerformance sharedInstance].dataCollectionEnabled)]); + constants[@"isInstrumentationEnabled"] = + @([RCTConvert BOOL:@([FIRPerformance sharedInstance].instrumentationEnabled)]); return constants; } @@ -73,7 +75,6 @@ + (BOOL)requiresMainQueueSetup { : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) { [FIRPerformance sharedInstance].dataCollectionEnabled = (BOOL)enabled; - [FIRPerformance sharedInstance].instrumentationEnabled = (BOOL)enabled; resolve([NSNull null]); } @@ -203,4 +204,12 @@ + (BOOL)requiresMainQueueSetup { resolve([NSNull null]); } +RCT_EXPORT_METHOD(instrumentationEnabled + : (BOOL)enabled resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + [FIRPerformance sharedInstance].instrumentationEnabled = (BOOL)enabled; + resolve([NSNull null]); +} + @end diff --git a/packages/perf/lib/index.d.ts b/packages/perf/lib/index.d.ts index bb43c94c86..75b2a17cc1 100644 --- a/packages/perf/lib/index.d.ts +++ b/packages/perf/lib/index.d.ts @@ -426,7 +426,22 @@ export namespace FirebasePerformanceTypes { * ``` */ isPerformanceCollectionEnabled: boolean; - + /** + * Determines whether to collect 'out of the box' (i.e already setup for Firebase Performance) events. + * This can be set for iOS. Android will always return "true" as it has to be set at gradle level. + */ + instrumentationEnabled: boolean; + /** + * Determines whether performance monitoring is enabled or disabled. + * + * #### Example + * + * ```js + * const isEnabled = firebase.perf().dataCollectionEnabled; + * console.log('Performance collection enabled: ', isEnabled); + * ``` + */ + dataCollectionEnabled: boolean; /** * Enables or disables performance monitoring. * @@ -436,8 +451,9 @@ export namespace FirebasePerformanceTypes { * // Disable performance monitoring collection * await firebase.perf().setPerformanceCollectionEnabled(false); * ``` - * - * @param enabled Should performance monitoring be enabled + * @deprecated prefer setting `dataCollectionEnabled = boolean`. + * @param enabled Should performance monitoring be enabled. For iOS only, this also toggles whether instrumentation + * is enabled. See: https://firebase.google.com/docs/reference/ios/firebaseperformance/api/reference/Classes/FIRPerformance#instrumentationenabled */ setPerformanceCollectionEnabled(enabled: boolean): Promise; @@ -470,7 +486,7 @@ export namespace FirebasePerformanceTypes { /** * Creates a ScreenTrace instance with the given identifier. - * Throws if hardware acceleration is diabled or if Android is 9.0 or 9.1. + * Throws if hardware acceleration is disabled or if Android is 9.0 or 9.1. * * #### Example * @@ -489,7 +505,7 @@ export namespace FirebasePerformanceTypes { /** * Creates a ScreenTrace instance with the given identifier and immediately starts it. - * Throws if hardware acceleration is diabled or if Android is 9.0 or 9.1. + * Throws if hardware acceleration is disabled or if Android is 9.0 or 9.1. * * #### Example * diff --git a/packages/perf/lib/index.js b/packages/perf/lib/index.js index a703a3c9ad..cf5a3c8e3a 100644 --- a/packages/perf/lib/index.js +++ b/packages/perf/lib/index.js @@ -21,11 +21,21 @@ import { FirebaseModule, getFirebaseRoot, } from '@react-native-firebase/app/lib/internal'; +import { Platform } from 'react-native'; import HttpMetric from './HttpMetric'; import Trace from './Trace'; import ScreenTrace from './ScreenTrace'; import version from './version'; +export { + getPerformance, + initializePerformance, + trace, + httpMetric, + newScreenTrace, + startScreenTrace, +} from './modular/index'; + const statics = {}; const namespace = 'perf'; @@ -48,19 +58,55 @@ class FirebasePerfModule extends FirebaseModule { constructor(...args) { super(...args); this._isPerformanceCollectionEnabled = this.native.isPerformanceCollectionEnabled; + this._instrumentationEnabled = this.native.isInstrumentationEnabled; } get isPerformanceCollectionEnabled() { return this._isPerformanceCollectionEnabled; } - setPerformanceCollectionEnabled(enabled) { + get instrumentationEnabled() { + return this._instrumentationEnabled; + } + + set instrumentationEnabled(enabled) { + if (!isBoolean(enabled)) { + throw new Error("firebase.perf().instrumentationEnabled = 'enabled' must be a boolean."); + } + if (Platform.OS == 'ios') { + // We don't change for android as it cannot be set from code, it is set at gradle build time. + this._instrumentationEnabled = enabled; + // No need to await, as it only takes effect on the next app run. + this.native.instrumentationEnabled(enabled); + } + } + + get dataCollectionEnabled() { + return this._isPerformanceCollectionEnabled; + } + + set dataCollectionEnabled(enabled) { + if (!isBoolean(enabled)) { + throw new Error("firebase.perf().dataCollectionEnabled = 'enabled' must be a boolean."); + } + this._isPerformanceCollectionEnabled = enabled; + this.native.setPerformanceCollectionEnabled(enabled); + } + + async setPerformanceCollectionEnabled(enabled) { if (!isBoolean(enabled)) { throw new Error( "firebase.perf().setPerformanceCollectionEnabled(*) 'enabled' must be a boolean.", ); } + if (Platform.OS == 'ios') { + // '_instrumentationEnabled' is updated here as well to maintain backward compatibility. See: + // https://github.com/invertase/react-native-firebase/commit/b705622e64d6ebf4ee026d50841e2404cf692f85 + this._instrumentationEnabled = enabled; + await this.native.instrumentationEnabled(enabled); + } + this._isPerformanceCollectionEnabled = enabled; return this.native.setPerformanceCollectionEnabled(enabled); } diff --git a/packages/perf/lib/modular/index.js b/packages/perf/lib/modular/index.js new file mode 100644 index 0000000000..b10136300c --- /dev/null +++ b/packages/perf/lib/modular/index.js @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { isBoolean } from '@react-native-firebase/app/lib/common'; +import { firebase } from '..'; + +/** + * Returns a Performance instance for the given app. + * @param app - FirebaseApp. Optional. + * @returns {Performance} + */ +export function getPerformance(app) { + if (app) { + return firebase.app(app.name).perf(); + } + + return firebase.app().perf(); +} + +/** + * Returns a Performance instance for the given app. + * @param app - FirebaseApp. Required. + * @param settings - PerformanceSettings. Set "dataCollectionEnabled" which will enable/disable Performance collection. + * @returns {Performance} + */ +export async function initializePerformance(app, settings) { + const perf = firebase.app(app.name).perf(); + + if (settings && isBoolean(settings.dataCollectionEnabled)) { + await perf.setPerformanceCollectionEnabled(settings.dataCollectionEnabled); + } + + return perf; +} + +/** + * Returns a Trace instance. + * @param perf - Performance instance + * @param identifier - A String to identify the Trace instance + * @returns {Trace} + */ +export function trace(perf, identifier) { + return perf.newTrace(identifier); +} + +/** + * Returns a HttpMetric instance. + * @param perf - Performance instance + * @param identifier - A String to identify the HttpMetric instance + * @returns {HttpMetric} + */ +export function httpMetric(perf, identifier, httpMethod) { + return perf.newHttpMetric(identifier, httpMethod); +} + +/** + * Creates a ScreenTrace instance with the given identifier. + * Throws if hardware acceleration is disabled or if Android is 9.0 or 9.1. + * @platform android Android !== 9.0.0 && Android !== 9.1.0 + * @param perf - Performance instance + * @param identifier Name of the trace, no leading or trailing whitespace allowed, no leading underscore '_' character allowed, max length is 100. + */ +export function newScreenTrace(perf, identifier) { + return perf.newScreenTrace(identifier); +} +/** + * Creates a ScreenTrace instance with the given identifier and immediately starts it. + * Throws if hardware acceleration is disabled or if Android is 9.0 or 9.1. + * @platform android Android !== 9.0.0 && Android !== 9.1.0 + * @param perf - Performance instance + * @param identifier Name of the screen + */ +export function startScreenTrace(perf, identifier) { + return perf.startScreenTrace(identifier); +} diff --git a/tests/app.js b/tests/app.js index 9aa7595012..5ca580e5d3 100644 --- a/tests/app.js +++ b/tests/app.js @@ -32,7 +32,7 @@ import '@react-native-firebase/in-app-messaging'; import '@react-native-firebase/installations'; import '@react-native-firebase/messaging'; import '@react-native-firebase/ml'; -import '@react-native-firebase/perf'; +import * as perfModular from '@react-native-firebase/perf'; import '@react-native-firebase/remote-config'; import '@react-native-firebase/storage'; import jet from 'jet/platform/react-native'; @@ -46,8 +46,11 @@ jet.exposeContextProperty('DeviceInfo', DeviceInfo); jet.exposeContextProperty('module', firebase); jet.exposeContextProperty('modular', modular); jet.exposeContextProperty('functionsModular', functionsModular); +jet.exposeContextProperty('perfModular', perfModular); + jet.exposeContextProperty('messagingModular', messagingModular); + firebase.database().useEmulator('localhost', 9000); firebase.auth().useEmulator('http://localhost:9099'); firebase.firestore().useEmulator('localhost', 8080); diff --git a/tests/e2e/globals.js b/tests/e2e/globals.js index e2e8899db7..7cf5f7c8b8 100644 --- a/tests/e2e/globals.js +++ b/tests/e2e/globals.js @@ -77,6 +77,11 @@ Object.defineProperty(global, 'functionsModular', { }, }); +Object.defineProperty(global, 'perfModular', { + get() { + return jet.perfModular; + }, +}); Object.defineProperty(global, 'messagingModular', { get() { return jet.messagingModular;