UNPKG

@tensorflow/tfjs-core

Version:

Hardware-accelerated JavaScript library for machine intelligence

348 lines 55.8 kB
/** * @license * Copyright 2018 Google LLC. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file 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 * as tf from '../index'; import { BROWSER_ENVS, describeWithFlags, runWithLock } from '../jasmine_util'; import { arrayBufferToBase64String, base64StringToArrayBuffer } from './io_utils'; import { browserLocalStorage, BrowserLocalStorage, BrowserLocalStorageManager, localStorageRouter, purgeLocalStorageArtifacts } from './local_storage'; describeWithFlags('LocalStorage', BROWSER_ENVS, () => { // Test data. const modelTopology1 = { 'class_name': 'Sequential', 'keras_version': '2.1.4', 'config': [{ 'class_name': 'Dense', 'config': { 'kernel_initializer': { 'class_name': 'VarianceScaling', 'config': { 'distribution': 'uniform', 'scale': 1.0, 'seed': null, 'mode': 'fan_avg' } }, 'name': 'dense', 'kernel_constraint': null, 'bias_regularizer': null, 'bias_constraint': null, 'dtype': 'float32', 'activation': 'linear', 'trainable': true, 'kernel_regularizer': null, 'bias_initializer': { 'class_name': 'Zeros', 'config': {} }, 'units': 1, 'batch_input_shape': [null, 3], 'use_bias': true, 'activity_regularizer': null } }], 'backend': 'tensorflow' }; const weightSpecs1 = [ { name: 'dense/kernel', shape: [3, 1], dtype: 'float32', }, { name: 'dense/bias', shape: [1], dtype: 'float32', } ]; const weightData1 = new ArrayBuffer(16); const trainingConfig1 = { loss: 'categorical_crossentropy', metrics: ['accuracy'], optimizer_config: { class_name: 'SGD', config: { learningRate: 0.1 } } }; const artifacts1 = { modelTopology: modelTopology1, weightSpecs: weightSpecs1, weightData: weightData1, format: 'layers-model', generatedBy: 'TensorFlow.js v0.0.0', convertedBy: '1.13.1', signature: null, userDefinedMetadata: {}, modelInitializer: {}, initializerSignature: null, trainingConfig: trainingConfig1, }; const artifactsV0 = { modelTopology: modelTopology1, weightSpecs: weightSpecs1, weightData: weightData1 }; function findOverflowingByteSize() { const LS = window.localStorage; const probeKey = `tfjs_test_probe_values_${new Date().getTime()}_${Math.random()}`; const minKilobytes = 200; const stepKilobytes = 200; const maxKilobytes = 40000; for (let kilobytes = minKilobytes; kilobytes < maxKilobytes; kilobytes += stepKilobytes) { const bytes = kilobytes * 1024; const data = new ArrayBuffer(bytes); try { const encoded = arrayBufferToBase64String(data); LS.setItem(probeKey, encoded); } catch (err) { return bytes; } LS.removeItem(probeKey); } throw new Error(`Unable to determined overflowing byte size up to ${maxKilobytes} kB.`); } beforeEach(() => { purgeLocalStorageArtifacts(); }); afterEach(() => { purgeLocalStorageArtifacts(); }); it('Save artifacts succeeds', runWithLock(async () => { const testStartDate = new Date(); const handler = tf.io.getSaveHandlers('localstorage://foo/FooModel')[0]; const saveResult = await handler.save(artifacts1); expect(saveResult.modelArtifactsInfo.dateSaved.getTime()) .toBeGreaterThanOrEqual(testStartDate.getTime()); // Note: The following two assertions work only because there is no // non-ASCII characters in `modelTopology1` and `weightSpecs1`. expect(saveResult.modelArtifactsInfo.modelTopologyBytes) .toEqual(JSON.stringify(modelTopology1).length); expect(saveResult.modelArtifactsInfo.weightSpecsBytes) .toEqual(JSON.stringify(weightSpecs1).length); expect(saveResult.modelArtifactsInfo.weightDataBytes).toEqual(16); // Check the content of the saved items in local storage. const LS = window.localStorage; const info = JSON.parse(LS.getItem('tensorflowjs_models/foo/FooModel/info')); expect(Date.parse(info.dateSaved)) .toEqual(saveResult.modelArtifactsInfo.dateSaved.getTime()); expect(info.modelTopologyBytes) .toEqual(saveResult.modelArtifactsInfo.modelTopologyBytes); expect(info.weightSpecsBytes) .toEqual(saveResult.modelArtifactsInfo.weightSpecsBytes); expect(info.weightDataBytes) .toEqual(saveResult.modelArtifactsInfo.weightDataBytes); const topologyString = LS.getItem('tensorflowjs_models/foo/FooModel/model_topology'); expect(JSON.stringify(modelTopology1)).toEqual(topologyString); const weightSpecsString = LS.getItem('tensorflowjs_models/foo/FooModel/weight_specs'); expect(JSON.stringify(weightSpecs1)).toEqual(weightSpecsString); const weightDataBase64String = LS.getItem('tensorflowjs_models/foo/FooModel/weight_data'); expect(base64StringToArrayBuffer(weightDataBase64String)) .toEqual(weightData1); })); it('Save-load round trip succeeds', runWithLock(async () => { const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0]; await handler1.save(artifacts1); const handler2 = tf.io.getLoadHandlers('localstorage://FooModel')[0]; const loaded = await handler2.load(); expect(loaded.modelTopology).toEqual(modelTopology1); expect(loaded.weightSpecs).toEqual(weightSpecs1); expect(loaded.weightData).toEqual(weightData1); expect(loaded.format).toEqual('layers-model'); expect(loaded.generatedBy).toEqual('TensorFlow.js v0.0.0'); expect(loaded.convertedBy).toEqual('1.13.1'); expect(loaded.userDefinedMetadata).toEqual({}); expect(loaded.modelInitializer).toEqual({}); expect(loaded.initializerSignature).toBeUndefined(); expect(loaded.trainingConfig).toEqual(trainingConfig1); })); it('Save-load round trip succeeds: v0 format', runWithLock(async () => { const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0]; await handler1.save(artifactsV0); const handler2 = tf.io.getLoadHandlers('localstorage://FooModel')[0]; const loaded = await handler2.load(); expect(loaded.modelTopology).toEqual(modelTopology1); expect(loaded.weightSpecs).toEqual(weightSpecs1); expect(loaded.weightData).toEqual(weightData1); expect(loaded.format).toBeUndefined(); expect(loaded.generatedBy).toBeUndefined(); expect(loaded.convertedBy).toBeUndefined(); expect(loaded.userDefinedMetadata).toBeUndefined(); expect(loaded.trainingConfig).toBeUndefined(); expect(loaded.modelInitializer).toBeUndefined(); expect(loaded.initializerSignature).toBeUndefined(); expect(loaded.trainingConfig).toBeUndefined(); })); it('Loading nonexistent model fails.', runWithLock(async () => { const handler = tf.io.getSaveHandlers('localstorage://NonexistentModel')[0]; try { await handler.load(); } catch (err) { expect(err.message) .toEqual('In local storage, there is no model with name ' + '\'NonexistentModel\''); return; // Success } fail('Loading nonexistent model succeeded unexpectedly.'); })); it('Loading model with missing topology fails.', runWithLock(async () => { const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0]; await handler1.save(artifacts1); // Manually remove the topology item from local storage. window.localStorage.removeItem('tensorflowjs_models/FooModel/model_topology'); const handler2 = tf.io.getLoadHandlers('localstorage://FooModel')[0]; try { await handler2.load(); } catch (err) { expect(err.message) .toEqual('In local storage, the topology of model ' + '\'FooModel\' is missing.'); return; // Success } fail('Loading of model with missing topology succeeded unexpectedly.'); })); it('Loading model with missing weight specs fails.', runWithLock(async () => { const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0]; await handler1.save(artifacts1); // Manually remove the weight specs item from local storage. window.localStorage.removeItem('tensorflowjs_models/FooModel/weight_specs'); const handler2 = tf.io.getLoadHandlers('localstorage://FooModel')[0]; try { await handler2.load(); } catch (err) { expect(err.message) .toEqual('In local storage, the weight specs of model ' + '\'FooModel\' are missing.'); return; // Success } fail('Loading of model with missing weight specs ' + 'succeeded unexpectedly.'); })); it('Loading model with missing weight data fails.', runWithLock(async () => { const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0]; await handler1.save(artifacts1); // Manually remove the weight data item from local storage. window.localStorage.removeItem('tensorflowjs_models/FooModel/weight_data'); const handler2 = tf.io.getLoadHandlers('localstorage://FooModel')[0]; try { await handler2.load(); fail('Loading of model with missing weight data ' + 'succeeded unexpectedly.'); } catch (err) { expect(err.message) .toEqual('In local storage, the binary weight values of model ' + '\'FooModel\' are missing.'); } })); it('Data size too large leads to error thrown', runWithLock(async () => { const overflowByteSize = findOverflowingByteSize(); const overflowArtifacts = { modelTopology: modelTopology1, weightSpecs: weightSpecs1, weightData: new ArrayBuffer(overflowByteSize), }; const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0]; try { await handler1.save(overflowArtifacts); fail('Saving of model of overflowing-size weight data succeeded ' + 'unexpectedly.'); } catch (err) { expect(err.message .indexOf('Failed to save model \'FooModel\' to local storage')) .toEqual(0); } })); it('Null, undefined or empty modelPath throws Error', () => { expect(() => browserLocalStorage(null)) .toThrowError(/local storage, modelPath must not be null, undefined or empty/); expect(() => browserLocalStorage(undefined)) .toThrowError(/local storage, modelPath must not be null, undefined or empty/); expect(() => browserLocalStorage('')) .toThrowError(/local storage, modelPath must not be null, undefined or empty./); }); it('router', () => { expect(localStorageRouter('localstorage://bar') instanceof BrowserLocalStorage) .toEqual(true); expect(localStorageRouter('indexeddb://bar')).toBeNull(); expect(localStorageRouter('qux')).toBeNull(); }); it('Manager: List models: 0 result', runWithLock(async () => { // Before any model is saved, listModels should return empty result. const out = await new BrowserLocalStorageManager().listModels(); expect(out).toEqual({}); })); it('Manager: List models: 1 result', runWithLock(async () => { const handler = tf.io.getSaveHandlers('localstorage://baz/QuxModel')[0]; const saveResult = await handler.save(artifacts1); // After successful saving, there should be one model. const out = await new BrowserLocalStorageManager().listModels(); if (Object.keys(out).length !== 1) { console.log(JSON.stringify(out, null, 2)); } expect(Object.keys(out).length).toEqual(1); expect(out['baz/QuxModel'].modelTopologyType) .toEqual(saveResult.modelArtifactsInfo.modelTopologyType); expect(out['baz/QuxModel'].modelTopologyBytes) .toEqual(saveResult.modelArtifactsInfo.modelTopologyBytes); expect(out['baz/QuxModel'].weightSpecsBytes) .toEqual(saveResult.modelArtifactsInfo.weightSpecsBytes); expect(out['baz/QuxModel'].weightDataBytes) .toEqual(saveResult.modelArtifactsInfo.weightDataBytes); })); it('Manager: List models: 2 results', runWithLock(async () => { // First, save a model. const handler1 = tf.io.getSaveHandlers('localstorage://QuxModel')[0]; const saveResult1 = await handler1.save(artifacts1); // Then, save the model under another path. const handler2 = tf.io.getSaveHandlers('localstorage://repeat/QuxModel')[0]; const saveResult2 = await handler2.save(artifacts1); // After successful saving, there should be two models. const out = await new BrowserLocalStorageManager().listModels(); if (Object.keys(out).length !== 2) { console.log(JSON.stringify(out, null, 2)); } expect(Object.keys(out).length).toEqual(2); expect(out['QuxModel'].modelTopologyType) .toEqual(saveResult1.modelArtifactsInfo.modelTopologyType); expect(out['QuxModel'].modelTopologyBytes) .toEqual(saveResult1.modelArtifactsInfo.modelTopologyBytes); expect(out['QuxModel'].weightSpecsBytes) .toEqual(saveResult1.modelArtifactsInfo.weightSpecsBytes); expect(out['QuxModel'].weightDataBytes) .toEqual(saveResult1.modelArtifactsInfo.weightDataBytes); expect(out['repeat/QuxModel'].modelTopologyType) .toEqual(saveResult2.modelArtifactsInfo.modelTopologyType); expect(out['repeat/QuxModel'].modelTopologyBytes) .toEqual(saveResult2.modelArtifactsInfo.modelTopologyBytes); expect(out['repeat/QuxModel'].weightSpecsBytes) .toEqual(saveResult2.modelArtifactsInfo.weightSpecsBytes); expect(out['repeat/QuxModel'].weightDataBytes) .toEqual(saveResult2.modelArtifactsInfo.weightDataBytes); })); it('Manager: Successful deleteModel', runWithLock(async () => { // First, save a model. const handler1 = tf.io.getSaveHandlers('localstorage://QuxModel')[0]; await handler1.save(artifacts1); // Then, save the model under another path. const handler2 = tf.io.getSaveHandlers('localstorage://repeat/QuxModel')[0]; await handler2.save(artifacts1); // After successful saving, delete the first save, and then // `listModel` should give only one result. const manager = new BrowserLocalStorageManager(); await manager.removeModel('QuxModel'); const out = await manager.listModels(); expect(Object.keys(out)).toEqual(['repeat/QuxModel']); })); }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9jYWxfc3RvcmFnZV90ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vdGZqcy1jb3JlL3NyYy9pby9sb2NhbF9zdG9yYWdlX3Rlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBRUgsT0FBTyxLQUFLLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFDL0IsT0FBTyxFQUFDLFlBQVksRUFBRSxpQkFBaUIsRUFBRSxXQUFXLEVBQUMsTUFBTSxpQkFBaUIsQ0FBQztBQUM3RSxPQUFPLEVBQUMseUJBQXlCLEVBQUUseUJBQXlCLEVBQUMsTUFBTSxZQUFZLENBQUM7QUFDaEYsT0FBTyxFQUFDLG1CQUFtQixFQUFFLG1CQUFtQixFQUFFLDBCQUEwQixFQUFFLGtCQUFrQixFQUFFLDBCQUEwQixFQUFDLE1BQU0saUJBQWlCLENBQUM7QUFFckosaUJBQWlCLENBQUMsY0FBYyxFQUFFLFlBQVksRUFBRSxHQUFHLEVBQUU7SUFDbkQsYUFBYTtJQUNiLE1BQU0sY0FBYyxHQUFPO1FBQ3pCLFlBQVksRUFBRSxZQUFZO1FBQzFCLGVBQWUsRUFBRSxPQUFPO1FBQ3hCLFFBQVEsRUFBRSxDQUFDO2dCQUNULFlBQVksRUFBRSxPQUFPO2dCQUNyQixRQUFRLEVBQUU7b0JBQ1Isb0JBQW9CLEVBQUU7d0JBQ3BCLFlBQVksRUFBRSxpQkFBaUI7d0JBQy9CLFFBQVEsRUFBRTs0QkFDUixjQUFjLEVBQUUsU0FBUzs0QkFDekIsT0FBTyxFQUFFLEdBQUc7NEJBQ1osTUFBTSxFQUFFLElBQUk7NEJBQ1osTUFBTSxFQUFFLFNBQVM7eUJBQ2xCO3FCQUNGO29CQUNELE1BQU0sRUFBRSxPQUFPO29CQUNmLG1CQUFtQixFQUFFLElBQUk7b0JBQ3pCLGtCQUFrQixFQUFFLElBQUk7b0JBQ3hCLGlCQUFpQixFQUFFLElBQUk7b0JBQ3ZCLE9BQU8sRUFBRSxTQUFTO29CQUNsQixZQUFZLEVBQUUsUUFBUTtv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UUFDckUsTUFBTSxRQUFRLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBRWhDLDJDQUEyQztRQUMzQyxNQUFNLFFBQVEsR0FDVixFQUFFLENBQUMsRUFBRSxDQUFDLGVBQWUsQ0FBQyxnQ0FBZ0MsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQy9ELE1BQU0sUUFBUSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUVoQywyREFBMkQ7UUFDM0QsMkNBQTJDO1FBQzNDLE1BQU0sT0FBTyxHQUFHLElBQUksMEJBQTBCLEVBQUUsQ0FBQztRQUNqRCxNQUFNLE9BQU8sQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDdEMsTUFBTSxHQUFHLEdBQUcsTUFBTSxPQUFPLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDdkMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDLENBQUM7SUFDeEQsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNULENBQUMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IDIwMTggR29vZ2xlIExMQy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC5cbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAnTGljZW5zZScpO1xuICogeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLlxuICogWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0XG4gKlxuICogaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wXG4gKlxuICogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZVxuICogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gJ0FTIElTJyBCQVNJUyxcbiAqIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLlxuICogU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZFxuICogbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuXG4gKiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICovXG5cbmltcG9ydCAqIGFzIHRmIGZyb20gJy4uL2luZGV4JztcbmltcG9ydCB7QlJPV1NFUl9FTlZTLCBkZXNjcmliZVdpdGhGbGFncywgcnVuV2l0aExvY2t9IGZyb20gJy4uL2phc21pbmVfdXRpbCc7XG5pbXBvcnQge2FycmF5QnVmZmVyVG9CYXNlNjRTdHJpbmcsIGJhc2U2NFN0cmluZ1RvQXJyYXlCdWZmZXJ9IGZyb20gJy4vaW9fdXRpbHMnO1xuaW1wb3J0IHticm93c2VyTG9jYWxTdG9yYWdlLCBCcm93c2VyTG9jYWxTdG9yYWdlLCBCcm93c2VyTG9jYWxTdG9yYWdlTWFuYWdlciwgbG9jYWxTdG9yYWdlUm91dGVyLCBwdXJnZUxvY2FsU3RvcmFnZUFydGlmYWN0c30gZnJvbSAnLi9sb2NhbF9zdG9yYWdlJztcblxuZGVzY3JpYmVXaXRoRmxhZ3MoJ0xvY2FsU3RvcmFnZScsIEJST1dTRVJfRU5WUywgKCkgPT4ge1xuICAvLyBUZXN0IGRhdGEuXG4gIGNvbnN0IG1vZGVsVG9wb2xvZ3kxOiB7fSA9IHtcbiAgICAnY2xhc3NfbmFtZSc6ICdTZXF1ZW50aWFsJyxcbiAgICAna2VyYXNfdmVyc2lvbic6ICcyLjEuNCcsXG4gICAgJ2NvbmZpZyc6IFt7XG4gICAgICAnY2xhc3NfbmFtZSc6ICdEZW5zZScsXG4gICAgICAnY29uZmlnJzoge1xuICAgICAgICAna2VybmVsX2luaXRpYWxpemVyJzoge1xuICAgICAgICAgICdjbGFzc19uYW1lJzogJ1ZhcmlhbmNlU2NhbGluZycsXG4gICAgICAgICAgJ2NvbmZpZyc6IHtcbiAgICAgICAgICAgICdkaXN0cmlidXRpb24nOiAndW5pZm9ybScsXG4gICAgICAgICAgICAnc2NhbGUnOiAxLjAsXG4gICAgICAgICAgICAnc2VlZCc6IG51bGwsXG4gICAgICAgICAgICAnbW9kZSc6ICdmYW5fYXZnJ1xuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgJ25hbWUnOiAnZGVuc2UnLFxuICAgICAgICAna2VybmVsX2NvbnN0cmFpbnQnOiBudWxsLFxuICAgICAgICAnYmlhc19yZWd1bGFyaXplcic6IG51bGwsXG4gICAgICAgICdiaWFzX2NvbnN0cmFpbnQnOiBudWxsLFxuICAgICAgICAnZHR5cGUnOiAnZmxvYXQzMicsXG4gICAgICAgICdhY3RpdmF0aW9uJzogJ2xpbmVhcicsXG4gICAgICAgICd0cmFpbmFibGUnOiB0cnVlLFxuICAgICAgICAna2VybmVsX3JlZ3VsYXJpemVyJzogbnVsbCxcbiAgICAgICAgJ2JpYXNfaW5pdGlhbGl6ZXInOiB7J2NsYXNzX25hbWUnOiAnWmVyb3MnLCAnY29uZmlnJzoge319LFxuICAgICAgICAndW5pdHMnOiAxLFxuICAgICAgICAnYmF0Y2hfaW5wdXRfc2hhcGUnOiBbbnVsbCwgM10sXG4gICAgICAgICd1c2VfYmlhcyc6IHRydWUsXG4gICAgICAgICdhY3Rpdml0eV9yZWd1bGFyaXplcic6IG51bGxcbiAgICAgIH1cbiAgICB9XSxcbiAgICAnYmFja2VuZCc6ICd0ZW5zb3JmbG93J1xuICB9O1xuICBjb25zdCB3ZWlnaHRTcGVjczE6IHRmLmlvLldlaWdodHNNYW5pZmVzdEVudHJ5W10gPSBbXG4gICAge1xuICAgICAgbmFtZTogJ2RlbnNlL2tlcm5lbCcsXG4gICAgICBzaGFwZTogWzMsIDFdLFxuICAgICAgZHR5cGU6ICdmbG9hdDMyJyxcbiAgICB9LFxuICAgIHtcbiAgICAgIG5hbWU6ICdkZW5zZS9iaWFzJyxcbiAgICAgIHNoYXBlOiBbMV0sXG4gICAgICBkdHlwZTogJ2Zsb2F0MzInLFxuICAgIH1cbiAgXTtcbiAgY29uc3Qgd2VpZ2h0RGF0YTEgPSBuZXcgQXJyYXlCdWZmZXIoMTYpO1xuICBjb25zdCB0cmFpbmluZ0NvbmZpZzE6IHRmLmlvLlRyYWluaW5nQ29uZmlnID0ge1xuICAgIGxvc3M6ICdjYXRlZ29yaWNhbF9jcm9zc2VudHJvcHknLFxuICAgIG1ldHJpY3M6IFsnYWNjdXJhY3knXSxcbiAgICBvcHRpbWl6ZXJfY29uZmlnOiB7Y2xhc3NfbmFtZTogJ1NHRCcsIGNvbmZpZzoge2xlYXJuaW5nUmF0ZTogMC4xfX1cbiAgfTtcblxuICBjb25zdCBhcnRpZmFjdHMxOiB0Zi5pby5Nb2RlbEFydGlmYWN0cyA9IHtcbiAgICBtb2RlbFRvcG9sb2d5OiBtb2RlbFRvcG9sb2d5MSxcbiAgICB3ZWlnaHRTcGVjczogd2VpZ2h0U3BlY3MxLFxuICAgIHdlaWdodERhdGE6IHdlaWdodERhdGExLFxuICAgIGZvcm1hdDogJ2xheWVycy1tb2RlbCcsXG4gICAgZ2VuZXJhdGVkQnk6ICdUZW5zb3JGbG93LmpzIHYwLjAuMCcsXG4gICAgY29udmVydGVkQnk6ICcxLjEzLjEnLFxuICAgIHNpZ25hdHVyZTogbnVsbCxcbiAgICB1c2VyRGVmaW5lZE1ldGFkYXRhOiB7fSxcbiAgICBtb2RlbEluaXRpYWxpemVyOiB7fSxcbiAgICBpbml0aWFsaXplclNpZ25hdHVyZTogbnVsbCxcbiAgICB0cmFpbmluZ0NvbmZpZzogdHJhaW5pbmdDb25maWcxLFxuICB9O1xuXG4gIGNvbnN0IGFydGlmYWN0c1YwOiB0Zi5pby5Nb2RlbEFydGlmYWN0cyA9IHtcbiAgICBtb2RlbFRvcG9sb2d5OiBtb2RlbFRvcG9sb2d5MSxcbiAgICB3ZWlnaHRTcGVjczogd2VpZ2h0U3BlY3MxLFxuICAgIHdlaWdodERhdGE6IHdlaWdodERhdGExXG4gIH07XG5cbiAgZnVuY3Rpb24gZmluZE92ZXJmbG93aW5nQnl0ZVNpemUoKTogbnVtYmVyIHtcbiAgICBjb25zdCBMUyA9IHdpbmRvdy5sb2NhbFN0b3JhZ2U7XG4gICAgY29uc3QgcHJvYmVLZXkgPVxuICAgICAgICBgdGZqc190ZXN0X3Byb2JlX3ZhbHVlc18ke25ldyBEYXRlKCkuZ2V0VGltZSgpfV8ke01hdGgucmFuZG9tKCl9YDtcbiAgICBjb25zdCBtaW5LaWxvYnl0ZXMgPSAyMDA7XG4gICAgY29uc3Qgc3RlcEtpbG9ieXRlcyA9IDIwMDtcbiAgICBjb25zdCBtYXhLaWxvYnl0ZXMgPSA0MDAwMDtcbiAgICBmb3IgKGxldCBraWxvYnl0ZXMgPSBtaW5LaWxvYnl0ZXM7IGtpbG9ieXRlcyA8IG1heEtpbG9ieXRlcztcbiAgICAgICAgIGtpbG9ieXRlcyArPSBzdGVwS2lsb2J5dGVzKSB7XG4gICAgICBjb25zdCBieXRlcyA9IGtpbG9ieXRlcyAqIDEwMjQ7XG4gICAgICBjb25zdCBkYXRhID0gbmV3IEFycmF5QnVmZmVyKGJ5dGVzKTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGVuY29kZWQgPSBhcnJheUJ1ZmZlclRvQmFzZTY0U3RyaW5nKGRhdGEpO1xuICAgICAgICBMUy5zZXRJdGVtKHByb2JlS2V5LCBlbmNvZGVkKTtcbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICByZXR1cm4gYnl0ZXM7XG4gICAgICB9XG4gICAgICBMUy5yZW1vdmVJdGVtKHByb2JlS2V5KTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgVW5hYmxlIHRvIGRldGVybWluZWQgb3ZlcmZsb3dpbmcgYnl0ZSBzaXplIHVwIHRvICR7bWF4S2lsb2J5dGVzfSBrQi5gKTtcbiAgfVxuXG4gIGJlZm9yZUVhY2goKCkgPT4ge1xuICAgIHB1cmdlTG9jYWxTdG9yYWdlQXJ0aWZhY3RzKCk7XG4gIH0pO1xuXG4gIGFmdGVyRWFjaCgoKSA9PiB7XG4gICAgcHVyZ2VMb2NhbFN0b3JhZ2VBcnRpZmFjdHMoKTtcbiAgfSk7XG5cbiAgaXQoJ1NhdmUgYXJ0aWZhY3RzIHN1Y2NlZWRzJywgcnVuV2l0aExvY2soYXN5bmMgKCkgPT4ge1xuICAgICAgIGNvbnN0IHRlc3RTdGFydERhdGUgPSBuZXcgRGF0ZSgpO1xuICAgICAgIGNvbnN0IGhhbmRsZXIgPSB0Zi5pby5nZXRTYXZlSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL2Zvby9Gb29Nb2RlbCcpWzBdO1xuICAgICAgIGNvbnN0IHNhdmVSZXN1bHQgPSBhd2FpdCBoYW5kbGVyLnNhdmUoYXJ0aWZhY3RzMSk7XG5cbiAgICAgICBleHBlY3Qoc2F2ZVJlc3VsdC5tb2RlbEFydGlmYWN0c0luZm8uZGF0ZVNhdmVkLmdldFRpbWUoKSlcbiAgICAgICAgICAgLnRvQmVHcmVhdGVyVGhhbk9yRXF1YWwodGVzdFN0YXJ0RGF0ZS5nZXRUaW1lKCkpO1xuICAgICAgIC8vIE5vdGU6IFRoZSBmb2xsb3dpbmcgdHdvIGFzc2VydGlvbnMgd29yayBvbmx5IGJlY2F1c2UgdGhlcmUgaXMgbm9cbiAgICAgICAvLyAgIG5vbi1BU0NJSSBjaGFyYWN0ZXJzIGluIGBtb2RlbFRvcG9sb2d5MWAgYW5kIGB3ZWlnaHRTcGVjczFgLlxuICAgICAgIGV4cGVjdChzYXZlUmVzdWx0Lm1vZGVsQXJ0aWZhY3RzSW5mby5tb2RlbFRvcG9sb2d5Qnl0ZXMpXG4gICAgICAgICAgIC50b0VxdWFsKEpTT04uc3RyaW5naWZ5KG1vZGVsVG9wb2xvZ3kxKS5sZW5ndGgpO1xuICAgICAgIGV4cGVjdChzYXZlUmVzdWx0Lm1vZGVsQXJ0aWZhY3RzSW5mby53ZWlnaHRTcGVjc0J5dGVzKVxuICAgICAgICAgICAudG9FcXVhbChKU09OLnN0cmluZ2lmeSh3ZWlnaHRTcGVjczEpLmxlbmd0aCk7XG4gICAgICAgZXhwZWN0KHNhdmVSZXN1bHQubW9kZWxBcnRpZmFjdHNJbmZvLndlaWdodERhdGFCeXRlcykudG9FcXVhbCgxNik7XG5cbiAgICAgICAvLyBDaGVjayB0aGUgY29udGVudCBvZiB0aGUgc2F2ZWQgaXRlbXMgaW4gbG9jYWwgc3RvcmFnZS5cbiAgICAgICBjb25zdCBMUyA9IHdpbmRvdy5sb2NhbFN0b3JhZ2U7XG4gICAgICAgY29uc3QgaW5mbyA9XG4gICAgICAgICAgIEpTT04ucGFyc2UoTFMuZ2V0SXRlbSgndGVuc29yZmxvd2pzX21vZGVscy9mb28vRm9vTW9kZWwvaW5mbycpKTtcbiAgICAgICBleHBlY3QoRGF0ZS5wYXJzZShpbmZvLmRhdGVTYXZlZCkpXG4gICAgICAgICAgIC50b0VxdWFsKHNhdmVSZXN1bHQubW9kZWxBcnRpZmFjdHNJbmZvLmRhdGVTYXZlZC5nZXRUaW1lKCkpO1xuICAgICAgIGV4cGVjdChpbmZvLm1vZGVsVG9wb2xvZ3lCeXRlcylcbiAgICAgICAgICAgLnRvRXF1YWwoc2F2ZVJlc3VsdC5tb2RlbEFydGlmYWN0c0luZm8ubW9kZWxUb3BvbG9neUJ5dGVzKTtcbiAgICAgICBleHBlY3QoaW5mby53ZWlnaHRTcGVjc0J5dGVzKVxuICAgICAgICAgICAudG9FcXVhbChzYXZlUmVzdWx0Lm1vZGVsQXJ0aWZhY3RzSW5mby53ZWlnaHRTcGVjc0J5dGVzKTtcbiAgICAgICBleHBlY3QoaW5mby53ZWlnaHREYXRhQnl0ZXMpXG4gICAgICAgICAgIC50b0VxdWFsKHNhdmVSZXN1bHQubW9kZWxBcnRpZmFjdHNJbmZvLndlaWdodERhdGFCeXRlcyk7XG5cbiAgICAgICBjb25zdCB0b3BvbG9neVN0cmluZyA9XG4gICAgICAgICAgIExTLmdldEl0ZW0oJ3RlbnNvcmZsb3dqc19tb2RlbHMvZm9vL0Zvb01vZGVsL21vZGVsX3RvcG9sb2d5Jyk7XG4gICAgICAgZXhwZWN0KEpTT04uc3RyaW5naWZ5KG1vZGVsVG9wb2xvZ3kxKSkudG9FcXVhbCh0b3BvbG9neVN0cmluZyk7XG5cbiAgICAgICBjb25zdCB3ZWlnaHRTcGVjc1N0cmluZyA9XG4gICAgICAgICAgIExTLmdldEl0ZW0oJ3RlbnNvcmZsb3dqc19tb2RlbHMvZm9vL0Zvb01vZGVsL3dlaWdodF9zcGVjcycpO1xuICAgICAgIGV4cGVjdChKU09OLnN0cmluZ2lmeSh3ZWlnaHRTcGVjczEpKS50b0VxdWFsKHdlaWdodFNwZWNzU3RyaW5nKTtcblxuICAgICAgIGNvbnN0IHdlaWdodERhdGFCYXNlNjRTdHJpbmcgPVxuICAgICAgICAgICBMUy5nZXRJdGVtKCd0ZW5zb3JmbG93anNfbW9kZWxzL2Zvby9Gb29Nb2RlbC93ZWlnaHRfZGF0YScpO1xuICAgICAgIGV4cGVjdChiYXNlNjRTdHJpbmdUb0FycmF5QnVmZmVyKHdlaWdodERhdGFCYXNlNjRTdHJpbmcpKVxuICAgICAgICAgICAudG9FcXVhbCh3ZWlnaHREYXRhMSk7XG4gICAgIH0pKTtcblxuICBpdCgnU2F2ZS1sb2FkIHJvdW5kIHRyaXAgc3VjY2VlZHMnLCBydW5XaXRoTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICAgY29uc3QgaGFuZGxlcjEgPSB0Zi5pby5nZXRTYXZlSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL0Zvb01vZGVsJylbMF07XG5cbiAgICAgICBhd2FpdCBoYW5kbGVyMS5zYXZlKGFydGlmYWN0czEpO1xuICAgICAgIGNvbnN0IGhhbmRsZXIyID0gdGYuaW8uZ2V0TG9hZEhhbmRsZXJzKCdsb2NhbHN0b3JhZ2U6Ly9Gb29Nb2RlbCcpWzBdO1xuICAgICAgIGNvbnN0IGxvYWRlZCA9IGF3YWl0IGhhbmRsZXIyLmxvYWQoKTtcbiAgICAgICBleHBlY3QobG9hZGVkLm1vZGVsVG9wb2xvZ3kpLnRvRXF1YWwobW9kZWxUb3BvbG9neTEpO1xuICAgICAgIGV4cGVjdChsb2FkZWQud2VpZ2h0U3BlY3MpLnRvRXF1YWwod2VpZ2h0U3BlY3MxKTtcbiAgICAgICBleHBlY3QobG9hZGVkLndlaWdodERhdGEpLnRvRXF1YWwod2VpZ2h0RGF0YTEpO1xuICAgICAgIGV4cGVjdChsb2FkZWQuZm9ybWF0KS50b0VxdWFsKCdsYXllcnMtbW9kZWwnKTtcbiAgICAgICBleHBlY3QobG9hZGVkLmdlbmVyYXRlZEJ5KS50b0VxdWFsKCdUZW5zb3JGbG93LmpzIHYwLjAuMCcpO1xuICAgICAgIGV4cGVjdChsb2FkZWQuY29udmVydGVkQnkpLnRvRXF1YWwoJzEuMTMuMScpO1xuICAgICAgIGV4cGVjdChsb2FkZWQudXNlckRlZmluZWRNZXRhZGF0YSkudG9FcXVhbCh7fSk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC5tb2RlbEluaXRpYWxpemVyKS50b0VxdWFsKHt9KTtcbiAgICAgICBleHBlY3QobG9hZGVkLmluaXRpYWxpemVyU2lnbmF0dXJlKS50b0JlVW5kZWZpbmVkKCk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC50cmFpbmluZ0NvbmZpZykudG9FcXVhbCh0cmFpbmluZ0NvbmZpZzEpO1xuICAgICB9KSk7XG5cbiAgaXQoJ1NhdmUtbG9hZCByb3VuZCB0cmlwIHN1Y2NlZWRzOiB2MCBmb3JtYXQnLCBydW5XaXRoTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICAgY29uc3QgaGFuZGxlcjEgPSB0Zi5pby5nZXRTYXZlSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL0Zvb01vZGVsJylbMF07XG5cbiAgICAgICBhd2FpdCBoYW5kbGVyMS5zYXZlKGFydGlmYWN0c1YwKTtcbiAgICAgICBjb25zdCBoYW5kbGVyMiA9IHRmLmlvLmdldExvYWRIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vRm9vTW9kZWwnKVswXTtcbiAgICAgICBjb25zdCBsb2FkZWQgPSBhd2FpdCBoYW5kbGVyMi5sb2FkKCk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC5tb2RlbFRvcG9sb2d5KS50b0VxdWFsKG1vZGVsVG9wb2xvZ3kxKTtcbiAgICAgICBleHBlY3QobG9hZGVkLndlaWdodFNwZWNzKS50b0VxdWFsKHdlaWdodFNwZWNzMSk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC53ZWlnaHREYXRhKS50b0VxdWFsKHdlaWdodERhdGExKTtcbiAgICAgICBleHBlY3QobG9hZGVkLmZvcm1hdCkudG9CZVVuZGVmaW5lZCgpO1xuICAgICAgIGV4cGVjdChsb2FkZWQuZ2VuZXJhdGVkQnkpLnRvQmVVbmRlZmluZWQoKTtcbiAgICAgICBleHBlY3QobG9hZGVkLmNvbnZlcnRlZEJ5KS50b0JlVW5kZWZpbmVkKCk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC51c2VyRGVmaW5lZE1ldGFkYXRhKS50b0JlVW5kZWZpbmVkKCk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC50cmFpbmluZ0NvbmZpZykudG9CZVVuZGVmaW5lZCgpO1xuICAgICAgIGV4cGVjdChsb2FkZWQubW9kZWxJbml0aWFsaXplcikudG9CZVVuZGVmaW5lZCgpO1xuICAgICAgIGV4cGVjdChsb2FkZWQuaW5pdGlhbGl6ZXJTaWduYXR1cmUpLnRvQmVVbmRlZmluZWQoKTtcbiAgICAgICBleHBlY3QobG9hZGVkLnRyYWluaW5nQ29uZmlnKS50b0JlVW5kZWZpbmVkKCk7XG4gICAgIH0pKTtcblxuICBpdCgnTG9hZGluZyBub25leGlzdGVudCBtb2RlbCBmYWlscy4nLCBydW5XaXRoTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICAgY29uc3QgaGFuZGxlciA9XG4gICAgICAgICAgIHRmLmlvLmdldFNhdmVIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vTm9uZXhpc3RlbnRNb2RlbCcpWzBdO1xuICAgICAgIHRyeSB7XG4gICAgICAgICBhd2FpdCBoYW5kbGVyLmxvYWQoKTtcbiAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgIGV4cGVjdChlcnIubWVzc2FnZSlcbiAgICAgICAgICAgICAudG9FcXVhbChcbiAgICAgICAgICAgICAgICAgJ0luIGxvY2FsIHN0b3JhZ2UsIHRoZXJlIGlzIG5vIG1vZGVsIHdpdGggbmFtZSAnICtcbiAgICAgICAgICAgICAgICAgJ1xcJ05vbmV4aXN0ZW50TW9kZWxcXCcnKTtcbiAgICAgICAgIHJldHVybjsgIC8vIFN1Y2Nlc3NcbiAgICAgICB9XG4gICAgICAgZmFpbCgnTG9hZGluZyBub25leGlzdGVudCBtb2RlbCBzdWNjZWVkZWQgdW5leHBlY3RlZGx5LicpO1xuICAgICB9KSk7XG5cbiAgaXQoJ0xvYWRpbmcgbW9kZWwgd2l0aCBtaXNzaW5nIHRvcG9sb2d5IGZhaWxzLicsIHJ1bldpdGhMb2NrKGFzeW5jICgpID0+IHtcbiAgICAgICBjb25zdCBoYW5kbGVyMSA9IHRmLmlvLmdldFNhdmVIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vRm9vTW9kZWwnKVswXTtcbiAgICAgICBhd2FpdCBoYW5kbGVyMS5zYXZlKGFydGlmYWN0czEpO1xuICAgICAgIC8vIE1hbnVhbGx5IHJlbW92ZSB0aGUgdG9wb2xvZ3kgaXRlbSBmcm9tIGxvY2FsIHN0b3JhZ2UuXG4gICAgICAgd2luZG93LmxvY2FsU3RvcmFnZS5yZW1vdmVJdGVtKFxuICAgICAgICAgICAndGVuc29yZmxvd2pzX21vZGVscy9Gb29Nb2RlbC9tb2RlbF90b3BvbG9neScpO1xuXG4gICAgICAgY29uc3QgaGFuZGxlcjIgPSB0Zi5pby5nZXRMb2FkSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL0Zvb01vZGVsJylbMF07XG4gICAgICAgdHJ5IHtcbiAgICAgICAgIGF3YWl0IGhhbmRsZXIyLmxvYWQoKTtcbiAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgIGV4cGVjdChlcnIubWVzc2FnZSlcbiAgICAgICAgICAgICAudG9FcXVhbChcbiAgICAgICAgICAgICAgICAgJ0luIGxvY2FsIHN0b3JhZ2UsIHRoZSB0b3BvbG9neSBvZiBtb2RlbCAnICtcbiAgICAgICAgICAgICAgICAgJ1xcJ0Zvb01vZGVsXFwnIGlzIG1pc3NpbmcuJyk7XG4gICAgICAgICByZXR1cm47ICAvLyBTdWNjZXNzXG4gICAgICAgfVxuICAgICAgIGZhaWwoJ0xvYWRpbmcgb2YgbW9kZWwgd2l0aCBtaXNzaW5nIHRvcG9sb2d5IHN1Y2NlZWRlZCB1bmV4cGVjdGVkbHkuJyk7XG4gICAgIH0pKTtcblxuICBpdCgnTG9hZGluZyBtb2RlbCB3aXRoIG1pc3Npbmcgd2VpZ2h0IHNwZWNzIGZhaWxzLicsIHJ1bldpdGhMb2NrKGFzeW5jICgpID0+IHtcbiAgICAgICBjb25zdCBoYW5kbGVyMSA9IHRmLmlvLmdldFNhdmVIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vRm9vTW9kZWwnKVswXTtcbiAgICAgICBhd2FpdCBoYW5kbGVyMS5zYXZlKGFydGlmYWN0czEpO1xuICAgICAgIC8vIE1hbnVhbGx5IHJlbW92ZSB0aGUgd2VpZ2h0IHNwZWNzIGl0ZW0gZnJvbSBsb2NhbCBzdG9yYWdlLlxuICAgICAgIHdpbmRvdy5sb2NhbFN0b3JhZ2UucmVtb3ZlSXRlbShcbiAgICAgICAgICAgJ3RlbnNvcmZsb3dqc19tb2RlbHMvRm9vTW9kZWwvd2VpZ2h0X3NwZWNzJyk7XG5cbiAgICAgICBjb25zdCBoYW5kbGVyMiA9IHRmLmlvLmdldExvYWRIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vRm9vTW9kZWwnKVswXTtcbiAgICAgICB0cnkge1xuICAgICAgICAgYXdhaXQgaGFuZGxlcjIubG9hZCgpO1xuICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgZXhwZWN0KGVyci5tZXNzYWdlKVxuICAgICAgICAgICAgIC50b0VxdWFsKFxuICAgICAgICAgICAgICAgICAnSW4gbG9jYWwgc3RvcmFnZSwgdGhlIHdlaWdodCBzcGVjcyBvZiBtb2RlbCAnICtcbiAgICAgICAgICAgICAgICAgJ1xcJ0Zvb01vZGVsXFwnIGFyZSBtaXNzaW5nLicpO1xuICAgICAgICAgcmV0dXJuOyAgLy8gU3VjY2Vzc1xuICAgICAgIH1cbiAgICAgICBmYWlsKFxuICAgICAgICAgICAnTG9hZGluZyBvZiBtb2RlbCB3aXRoIG1pc3Npbmcgd2VpZ2h0IHNwZWNzICcgK1xuICAgICAgICAgICAnc3VjY2VlZGVkIHVuZXhwZWN0ZWRseS4nKTtcbiAgICAgfSkpO1xuXG4gIGl0KCdMb2FkaW5nIG1vZGVsIHdpdGggbWlzc2luZyB3ZWlnaHQgZGF0YSBmYWlscy4nLCBydW5XaXRoTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICAgY29uc3QgaGFuZGxlcjEgPSB0Zi5pby5nZXRTYXZlSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL0Zvb01vZGVsJylbMF07XG4gICAgICAgYXdhaXQgaGFuZGxlcjEuc2F2ZShhcnRpZmFjdHMxKTtcblxuICAgICAgIC8vIE1hbnVhbGx5IHJlbW92ZSB0aGUgd2VpZ2h0IGRhdGEgaXRlbSBmcm9tIGxvY2FsIHN0b3JhZ2UuXG4gICAgICAgd2luZG93LmxvY2FsU3RvcmFnZS5yZW1vdmVJdGVtKFxuICAgICAgICAgICAndGVuc29yZmxvd2pzX21vZGVscy9Gb29Nb2RlbC93ZWlnaHRfZGF0YScpO1xuXG4gICAgICAgY29uc3QgaGFuZGxlcjIgPSB0Zi5pby5nZXRMb2FkSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL0Zvb01vZGVsJylbMF07XG4gICAgICAgdHJ5IHtcbiAgICAgICAgIGF3YWl0IGhhbmRsZXIyLmxvYWQoKTtcbiAgICAgICAgIGZhaWwoXG4gICAgICAgICAgICAgJ0xvYWRpbmcgb2YgbW9kZWwgd2l0aCBtaXNzaW5nIHdlaWdodCBkYXRhICcgK1xuICAgICAgICAgICAgICdzdWNjZWVkZWQgdW5leHBlY3RlZGx5LicpO1xuICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgZXhwZWN0KGVyci5tZXNzYWdlKVxuICAgICAgICAgICAgIC50b0VxdWFsKFxuICAgICAgICAgICAgICAgICAnSW4gbG9jYWwgc3RvcmFnZSwgdGhlIGJpbmFyeSB3ZWlnaHQgdmFsdWVzIG9mIG1vZGVsICcgK1xuICAgICAgICAgICAgICAgICAnXFwnRm9vTW9kZWxcXCcgYXJlIG1pc3NpbmcuJyk7XG4gICAgICAgfVxuICAgICB9KSk7XG5cbiAgaXQoJ0RhdGEgc2l6ZSB0b28gbGFyZ2UgbGVhZHMgdG8gZXJyb3IgdGhyb3duJywgcnVuV2l0aExvY2soYXN5bmMgKCkgPT4ge1xuICAgICAgIGNvbnN0IG92ZXJmbG93Qnl0ZVNpemUgPSBmaW5kT3ZlcmZsb3dpbmdCeXRlU2l6ZSgpO1xuICAgICAgIGNvbnN0IG92ZXJmbG93QXJ0aWZhY3RzOiB0Zi5pby5Nb2RlbEFydGlmYWN0cyA9IHtcbiAgICAgICAgIG1vZGVsVG9wb2xvZ3k6IG1vZGVsVG9wb2xvZ3kxLFxuICAgICAgICAgd2VpZ2h0U3BlY3M6IHdlaWdodFNwZWNzMSxcbiAgICAgICAgIHdlaWdodERhdGE6IG5ldyBBcnJheUJ1ZmZlcihvdmVyZmxvd0J5dGVTaXplKSxcbiAgICAgICB9O1xuICAgICAgIGNvbnN0IGhhbmRsZXIxID0gdGYuaW8uZ2V0U2F2ZUhhbmRsZXJzKCdsb2NhbHN0b3JhZ2U6Ly9Gb29Nb2RlbCcpWzBdO1xuICAgICAgIHRyeSB7XG4gICAgICAgICBhd2FpdCBoYW5kbGVyMS5zYXZlKG92ZXJmbG93QXJ0aWZhY3RzKTtcbiAgICAgICAgIGZhaWwoXG4gICAgICAgICAgICAgJ1NhdmluZyBvZiBtb2RlbCBvZiBvdmVyZmxvd2luZy1zaXplIHdlaWdodCBkYXRhIHN1Y2NlZWRlZCAnICtcbiAgICAgICAgICAgICAndW5leHBlY3RlZGx5LicpO1xuICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgZXhwZWN0KFxuICAgICAgICAgICAgIChlcnIubWVzc2FnZSBhcyBzdHJpbmcpXG4gICAgICAgICAgICAgICAgIC5pbmRleE9mKCdGYWlsZWQgdG8gc2F2ZSBtb2RlbCBcXCdGb29Nb2RlbFxcJyB0byBsb2NhbCBzdG9yYWdlJykpXG4gICAgICAgICAgICAgLnRvRXF1YWwoMCk7XG4gICAgICAgfVxuICAgICB9KSk7XG5cbiAgaXQoJ051bGwsIHVuZGVmaW5lZCBvciBlbXB0eSBtb2RlbFBhdGggdGhyb3dzIEVycm9yJywgKCkgPT4ge1xuICAgIGV4cGVjdCgoKSA9PiBicm93c2VyTG9jYWxTdG9yYWdlKG51bGwpKVxuICAgICAgICAudG9UaHJvd0Vycm9yKFxuICAgICAgICAgICAgL2xvY2FsIHN0b3JhZ2UsIG1vZGVsUGF0aCBtdXN0IG5vdCBiZSBudWxsLCB1bmRlZmluZWQgb3IgZW1wdHkvKTtcbiAgICBleHBlY3