From f997d3c026111a94cc4bf6fab2ce2ab39e88d560 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 11:37:22 -0700 Subject: [PATCH 01/10] chore: store nodejs build artifacts in placer (#1137) Source-Link: https://github.com/googleapis/synthtool/commit/3602660ae703daadcb7bc2f87bf601241665f3f8 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest@sha256:e6d785d6de3cab027f6213d95ccedab4cab3811b0d3172b78db2216faa182e32 Co-authored-by: Owl Bot --- .github/.OwlBot.lock.yaml | 4 ++-- .kokoro/publish.sh | 14 +++++++++++++- .kokoro/release/publish.cfg | 12 ++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 788f7a9f..0b836e11 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest - digest: sha256:fe04ae044dadf5ad88d979dbcc85e0e99372fb5d6316790341e6aca5e4e3fbc8 + digest: sha256:e6d785d6de3cab027f6213d95ccedab4cab3811b0d3172b78db2216faa182e32 diff --git a/.kokoro/publish.sh b/.kokoro/publish.sh index 949e3e1d..ca1d47af 100755 --- a/.kokoro/publish.sh +++ b/.kokoro/publish.sh @@ -27,4 +27,16 @@ NPM_TOKEN=$(cat $KOKORO_KEYSTORE_DIR/73713_google-cloud-npm-token-1) echo "//wombat-dressing-room.appspot.com/:_authToken=${NPM_TOKEN}" > ~/.npmrc npm install -npm publish --access=public --registry=https://wombat-dressing-room.appspot.com +npm pack . +# npm provides no way to specify, observe, or predict the name of the tarball +# file it generates. We have to look in the current directory for the freshest +# .tgz file. +TARBALL=$(ls -1 -t *.tgz | head -1) + +npm publish --access=public --registry=https://wombat-dressing-room.appspot.com "$TARBALL" + +# Kokoro collects *.tgz and package-lock.json files and stores them in Placer +# so we can generate SBOMs and attestations. +# However, we *don't* want Kokoro to collect package-lock.json and *.tgz files +# that happened to be installed with dependencies. +find node_modules -name package-lock.json -o -name "*.tgz" | xargs rm -f \ No newline at end of file diff --git a/.kokoro/release/publish.cfg b/.kokoro/release/publish.cfg index 6c9bd979..609f16b1 100644 --- a/.kokoro/release/publish.cfg +++ b/.kokoro/release/publish.cfg @@ -37,3 +37,15 @@ env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/cloud-debug-nodejs/.kokoro/publish.sh" } + +# Store the packages we uploaded to npmjs.org and their corresponding +# package-lock.jsons in Placer. That way, we have a record of exactly +# what we published, and which version of which tools we used to publish +# it, which we can use to generate SBOMs and attestations. +action { + define_artifacts { + regex: "github/**/*.tgz" + regex: "github/**/package-lock.json" + strip_prefix: "github" + } +} From 72040e0a296b1df7eefc8ee99313b635848ffc98 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 19 Apr 2023 12:26:40 +0200 Subject: [PATCH 02/10] chore(deps): update dependency @types/estree to v1.0.1 (#1138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@types/estree](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/estree) ([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped)) | [`1.0.0` -> `1.0.1`](https://renovatebot.com/diffs/npm/@types%2festree/1.0.0/1.0.1) | [![age](https://badges.renovateapi.com/packages/npm/@types%2festree/1.0.1/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/npm/@types%2festree/1.0.1/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/npm/@types%2festree/1.0.1/compatibility-slim/1.0.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/npm/@types%2festree/1.0.1/confidence-slim/1.0.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/cloud-debug-nodejs). --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac59727c..5ad8ad29 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@compodoc/compodoc": "1.1.19", "@types/acorn": "^4.0.2", "@types/console-log-level": "^1.4.0", - "@types/estree": "1.0.0", + "@types/estree": "1.0.1", "@types/extend": "^3.0.0", "@types/mocha": "^9.0.0", "@types/mv": "^2.1.0", From 81ae406bf41de8d0faf2b2dd0c7419164d017713 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 25 May 2023 13:44:13 +0000 Subject: [PATCH 03/10] docs: update docs-devsite.sh to use latest node-js-rad version (#1139) Co-authored-by: sofisl <55454395+sofisl@users.noreply.github.com> Source-Link: https://togithub.com/googleapis/synthtool/commit/b1ced7db5adee08cfa91d6b138679fceff32c004 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest@sha256:0527a86c10b67742c409dc726ba9a31ec4e69b0006e3d7a49b0e6686c59cdaa9 --- .github/.OwlBot.lock.yaml | 3 ++- .kokoro/release/docs-devsite.sh | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 0b836e11..21ad18bd 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest - digest: sha256:e6d785d6de3cab027f6213d95ccedab4cab3811b0d3172b78db2216faa182e32 + digest: sha256:0527a86c10b67742c409dc726ba9a31ec4e69b0006e3d7a49b0e6686c59cdaa9 +# created: 2023-05-24T20:32:43.844586914Z diff --git a/.kokoro/release/docs-devsite.sh b/.kokoro/release/docs-devsite.sh index 2198e67f..3596c1e4 100755 --- a/.kokoro/release/docs-devsite.sh +++ b/.kokoro/release/docs-devsite.sh @@ -25,5 +25,6 @@ if [[ -z "$CREDENTIALS" ]]; then fi npm install -npm install --no-save @google-cloud/cloud-rad@^0.2.5 -npx @google-cloud/cloud-rad \ No newline at end of file +npm install --no-save @google-cloud/cloud-rad@^0.3.7 +# publish docs to devsite +npx @google-cloud/cloud-rad . cloud-rad From 1b5314dddd2ed536263020a0cb1b40547ebb5908 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 25 May 2023 15:54:13 +0200 Subject: [PATCH 04/10] chore(deps): update dependency @compodoc/compodoc to v1.1.21 (#1140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@compodoc/compodoc](https://togithub.com/compodoc/compodoc) | [`1.1.19` -> `1.1.21`](https://renovatebot.com/diffs/npm/@compodoc%2fcompodoc/1.1.19/1.1.21) | [![age](https://badges.renovateapi.com/packages/npm/@compodoc%2fcompodoc/1.1.21/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/npm/@compodoc%2fcompodoc/1.1.21/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/npm/@compodoc%2fcompodoc/1.1.21/compatibility-slim/1.1.19)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/npm/@compodoc%2fcompodoc/1.1.21/confidence-slim/1.1.19)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
compodoc/compodoc ### [`v1.1.21`](https://togithub.com/compodoc/compodoc/blob/HEAD/CHANGELOG.md#​1121-httpsgithubcomcompodoccompodoccompare11201121) [Compare Source](https://togithub.com/compodoc/compodoc/compare/1.1.20...1.1.21) ##### Bug fixes - feat(app): downgrade Marked version ([e0a4b78](https://togithub.com/compodoc/compodoc/commit/e0a4b78)), closes [#​1349](https://togithub.com/compodoc/compodoc/issues/1349) ### [`v1.1.20`](https://togithub.com/compodoc/compodoc/blob/HEAD/CHANGELOG.md#​1120-httpsgithubcomcompodoccompodoccompare11191120---2023-05-23) [Compare Source](https://togithub.com/compodoc/compodoc/compare/1.1.19...1.1.20) ##### Merged - fix(Input): Add support for Object Expressions in Input decorators [#​1326](https://togithub.com/compodoc/compodoc/pull/1326), Thanks [valentinpalkovic](https://togithub.com/valentinpalkovic) - fix(app): overview depth [#​1310](https://togithub.com/compodoc/compodoc/pull/1310), Thanks [albeniraouf](https://togithub.com/albeniraouf) - Translates to Bulgarian [#​1312](https://togithub.com/compodoc/compodoc/pull/1312), Thanks [3phase](https://togithub.com/3phase) ##### Bug fixes - feat(app): Directive composition API for directives and components ([127076a](https://togithub.com/compodoc/compodoc/commit/127076a)), closes [#​1340](https://togithub.com/compodoc/compodoc/issues/1340) - feat(app): Required Inputs ([e1a5396](https://togithub.com/compodoc/compodoc/commit/e1a5396)), closes [#​1340](https://togithub.com/compodoc/compodoc/issues/1340) - feat(app): Standalone components, directives and pipes support ([cb02ca0](https://togithub.com/compodoc/compodoc/commit/cb02ca0)), closes [#​1323](https://togithub.com/compodoc/compodoc/issues/1323) - fix(app): support exportAs for directives ([76a8f34](https://togithub.com/compodoc/compodoc/commit/76a8f34)), closes [#​1328](https://togithub.com/compodoc/compodoc/issues/1328) - feat(app): bump [@​compodoc/ngd-transformer](https://togithub.com/compodoc/ngd-transformer) ([ef9bd94](https://togithub.com/compodoc/compodoc/commit/ef9bd94)), closes [#​1311](https://togithub.com/compodoc/compodoc/issues/1311) - fix(app): service/injectable export in module providers ([34967a9](https://togithub.com/compodoc/compodoc/commit/34967a9)), closes [#​1290](https://togithub.com/compodoc/compodoc/issues/1290) - fix(app): missing rel attribute with \_blank links ([c8379e0](https://togithub.com/compodoc/compodoc/commit/c8379e0)), closes [#​1282](https://togithub.com/compodoc/compodoc/issues/1282) - feat(app): Add specific id in each html section ([03ac1ad](https://togithub.com/compodoc/compodoc/commit/03ac1ad)), closes [#​1241](https://togithub.com/compodoc/compodoc/issues/1241) - fix(app): Invalid links to a class when the class name includes an interface name ([047cedb](https://togithub.com/compodoc/compodoc/commit/047cedb)), closes [#​1239](https://togithub.com/compodoc/compodoc/issues/1239) - fix(routing): path wrongly resolved during routing analysis ([1722ca3](https://togithub.com/compodoc/compodoc/commit/1722ca3)), closes [#​1170](https://togithub.com/compodoc/compodoc/issues/1170)
--- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/cloud-debug-nodejs). --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ad8ad29..0e946686 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ } }, "devDependencies": { - "@compodoc/compodoc": "1.1.19", + "@compodoc/compodoc": "1.1.21", "@types/acorn": "^4.0.2", "@types/console-log-level": "^1.4.0", "@types/estree": "1.0.1", From 47a6fc8102774b4ae23a3ab5fd6a7a57ebb830a8 Mon Sep 17 00:00:00 2001 From: James McTavish Date: Thu, 25 May 2023 13:34:21 -0400 Subject: [PATCH 05/10] chore: remove dead code for supporting node < 10 (#1141) --- src/agent/v8/debugapi.ts | 36 +- src/agent/v8/legacy-debugapi.ts | 593 -------------------------------- test/test-v8debugapi.ts | 46 +-- 3 files changed, 5 insertions(+), 670 deletions(-) delete mode 100644 src/agent/v8/legacy-debugapi.ts diff --git a/src/agent/v8/debugapi.ts b/src/agent/v8/debugapi.ts index c6ec1277..1c7ae9e0 100644 --- a/src/agent/v8/debugapi.ts +++ b/src/agent/v8/debugapi.ts @@ -14,10 +14,10 @@ import consoleLogLevel = require('console-log-level'); import * as stackdriver from '../../types/stackdriver'; -import {DebugAgentConfig} from '../config'; +import {ResolvedDebugAgentConfig} from '../config'; import {ScanStats} from '../io/scanner'; import {SourceMapper} from '../io/sourcemapper'; -import * as utils from '../util/utils'; +import {InspectorDebugApi} from './inspector-debugapi'; export interface DebugApi { set( @@ -42,34 +42,6 @@ export interface DebugApi { numListeners_(): number; } -interface DebugApiConstructor { - new ( - logger: consoleLogLevel.Logger, - config: DebugAgentConfig, - jsFiles: ScanStats, - sourcemapper: SourceMapper - ): DebugApi; -} - -let debugApiConstructor: DebugApiConstructor; - -export function willUseInspector(nodeVersion?: string) { - // checking for null and undefined. - // eslint-disable-next-line eqeqeq - const version = nodeVersion != null ? nodeVersion : process.version; - return utils.satisfies(version, '>=10'); -} - -if (willUseInspector()) { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const inspectorapi = require('./inspector-debugapi'); - debugApiConstructor = inspectorapi.InspectorDebugApi; -} else { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const v8debugapi = require('./legacy-debugapi'); - debugApiConstructor = v8debugapi.V8DebugApi; -} - export const MODULE_WRAP_PREFIX_LENGTH = require('module') .wrap('☃') .indexOf('☃'); @@ -78,7 +50,7 @@ let singleton: DebugApi; export function create( logger: consoleLogLevel.Logger, - config: DebugAgentConfig, + config: ResolvedDebugAgentConfig, jsFiles: ScanStats, sourcemapper: SourceMapper ): DebugApi { @@ -88,6 +60,6 @@ export function create( singleton.disconnect(); } - singleton = new debugApiConstructor(logger, config, jsFiles, sourcemapper); + singleton = new InspectorDebugApi(logger, config, jsFiles, sourcemapper); return singleton; } diff --git a/src/agent/v8/legacy-debugapi.ts b/src/agent/v8/legacy-debugapi.ts deleted file mode 100644 index d14e0ccb..00000000 --- a/src/agent/v8/legacy-debugapi.ts +++ /dev/null @@ -1,593 +0,0 @@ -// Copyright 2017 Google LLC -// -// 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 acorn from 'acorn'; -import * as estree from 'estree'; -import * as path from 'path'; -import * as semver from 'semver'; -import * as vm from 'vm'; - -import {StatusMessage} from '../../client/stackdriver/status-message'; -import consoleLogLevel = require('console-log-level'); -import * as stackdriver from '../../types/stackdriver'; -import * as v8 from '../../types/v8'; -import {ResolvedDebugAgentConfig} from '../config'; -import {FileStats, ScanStats} from '../io/scanner'; -import {MapInfoOutput, SourceMapper} from '../io/sourcemapper'; -import * as state from '../state/legacy-state'; -import * as utils from '../util/utils'; - -import * as debugapi from './debugapi'; - -export class V8BreakpointData { - constructor( - public apiBreakpoint: stackdriver.Breakpoint, - public v8Breakpoint: v8.BreakPoint, - public parsedCondition: estree.Node, - // TODO: The code in this method assumes that `compile` exists. Verify - // that is correct. - // TODO: Update this so that `null|` is not needed for `compile`. - public compile: null | ((src: string) => string) - ) {} -} - -interface LegacyVm { - runInDebugContext: (context: string) => v8.Debug; -} - -export class V8DebugApi implements debugapi.DebugApi { - breakpoints: {[id: string]: V8BreakpointData} = {}; - sourcemapper: SourceMapper; - v8: v8.Debug; - config: ResolvedDebugAgentConfig; - fileStats: ScanStats; - listeners: {[id: string]: utils.LegacyListener} = {}; - v8Version: RegExpExecArray | null; - usePermanentListener: boolean; - logger: consoleLogLevel.Logger; - handleDebugEvents: ( - evt: v8.DebugEvent, - execState: v8.ExecutionState, - eventData: v8.BreakEvent - ) => void; - - numBreakpoints = 0; - - constructor( - logger: consoleLogLevel.Logger, - config: ResolvedDebugAgentConfig, - jsFiles: ScanStats, - sourcemapper: SourceMapper - ) { - this.sourcemapper = sourcemapper; - // This constructor is only used in situations where the legacy vm - // interface is used that has the `runInDebugContext` method. - this.v8 = (vm as {} as LegacyVm).runInDebugContext('Debug'); - this.config = config; - this.fileStats = jsFiles; - this.v8Version = /(\d+\.\d+\.\d+)\.\d+/.exec(process.versions.v8); - this.logger = logger; - this.usePermanentListener = semver.satisfies(this.v8Version![1], '>=4.5'); - this.handleDebugEvents = ( - evt: v8.DebugEvent, - execState: v8.ExecutionState, - eventData: v8.BreakEvent - ): void => { - try { - switch (evt) { - // TODO: Address the case where `v8` is `null`. - case this.v8.DebugEvent.Break: - eventData.breakPointsHit().forEach(hit => { - const num = hit.script_break_point().number(); - if (this.listeners[num].enabled) { - this.logger.info('>>>V8 breakpoint hit<<< number: ' + num); - this.listeners[num].listener(execState, eventData); - } - }); - break; - default: - } - } catch (e) { - this.logger.warn('Internal V8 error on breakpoint event: ' + e); - } - }; - if (this.usePermanentListener) { - this.logger.info('activating v8 breakpoint listener (permanent)'); - - this.v8.setListener(this.handleDebugEvents); - } - } - - set( - breakpoint: stackdriver.Breakpoint, - cb: (err: Error | null) => void - ): void { - if ( - !this.v8 || - !breakpoint || - typeof breakpoint.id === 'undefined' || // 0 is a valid id - !breakpoint.location || - !breakpoint.location.path || - !breakpoint.location.line - ) { - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.UNSPECIFIED, - utils.messages.INVALID_BREAKPOINT - ); - } - const baseScriptPath = path.normalize(breakpoint.location.path); - const mapInfoInput = this.sourcemapper.getMapInfoInput(baseScriptPath); - if (mapInfoInput === null) { - const extension = path.extname(baseScriptPath); - if (!this.config.javascriptFileExtensions.includes(extension)) { - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.BREAKPOINT_SOURCE_LOCATION, - utils.messages.COULD_NOT_FIND_OUTPUT_FILE - ); - } - this.setInternal(breakpoint, null /* mapInfo */, null /* compile */, cb); - } else { - const line = breakpoint.location.line; - const column = 0; - const mapInfo = this.sourcemapper.getMapInfoOutput( - line, - column, - mapInfoInput - ); - const compile = utils.getBreakpointCompiler(breakpoint); - if (breakpoint.condition && compile) { - try { - breakpoint.condition = compile(breakpoint.condition); - } catch (e) { - this.logger.info( - 'Unable to compile condition >> ' + breakpoint.condition + ' <<' - ); - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.BREAKPOINT_CONDITION, - utils.messages.ERROR_COMPILING_CONDITION - ); - } - } - - this.setInternal(breakpoint, mapInfo, compile, cb); - } - } - - clear( - breakpoint: stackdriver.Breakpoint, - cb: (err: Error | null) => void - ): void { - if (typeof breakpoint.id === 'undefined') { - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.BREAKPOINT_CONDITION, - utils.messages.V8_BREAKPOINT_CLEAR_ERROR - ); - } - const breakpointData = this.breakpoints[breakpoint.id]; - if (!breakpointData) { - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.BREAKPOINT_CONDITION, - utils.messages.V8_BREAKPOINT_CLEAR_ERROR - ); - } - const v8bp = breakpointData.v8Breakpoint; - this.v8.clearBreakPoint(v8bp.number()); - delete this.breakpoints[breakpoint.id]; - delete this.listeners[v8bp.number()]; - this.numBreakpoints--; - if (this.numBreakpoints === 0 && !this.usePermanentListener) { - // removed last breakpoint - this.logger.info('deactivating v8 breakpoint listener'); - this.v8.setListener(null); - } - setImmediate(() => { - cb(null); - }); - } - - wait( - breakpoint: stackdriver.Breakpoint, - callback: (err?: Error) => void - ): void { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const that = this; - const num = that.breakpoints[breakpoint.id].v8Breakpoint.number(); - const listener = this.onBreakpointHit.bind( - this, - breakpoint, - (err: Error | null) => { - that.listeners[num].enabled = false; - // This method is called from the debug event listener, which - // swallows all exception. We defer the callback to make sure the - // user errors aren't silenced. - setImmediate(() => { - callback(err || undefined); - }); - } - ); - - that.listeners[num] = {enabled: true, listener}; - } - - log( - breakpoint: stackdriver.Breakpoint, - print: (format: string, exps: string[]) => void, - shouldStop: () => boolean - ): void { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const that = this; - const num = that.breakpoints[breakpoint.id].v8Breakpoint.number(); - let logsThisSecond = 0; - let timesliceEnd = Date.now() + 1000; - // TODO: Determine why the Error argument is not used. - const listener = this.onBreakpointHit.bind(this, breakpoint, () => { - const currTime = Date.now(); - if (currTime > timesliceEnd) { - logsThisSecond = 0; - timesliceEnd = currTime + 1000; - } - print( - // TODO: Address the case where `breakpoint.logMessageFormat` is - // null - breakpoint.logMessageFormat!, - breakpoint.evaluatedExpressions.map(obj => JSON.stringify(obj)) - ); - logsThisSecond++; - if (shouldStop()) { - that.listeners[num].enabled = false; - } else { - if (logsThisSecond >= that.config.log.maxLogsPerSecond) { - that.listeners[num].enabled = false; - setTimeout(() => { - // listeners[num] may have been deleted by `clear` during the - // async hop. Make sure it is valid before setting a property - // on it. - if (!shouldStop() && that.listeners[num]) { - that.listeners[num].enabled = true; - } - }, that.config.log.logDelaySeconds * 1000); - } - } - }); - that.listeners[num] = {enabled: true, listener}; - } - - disconnect(): void { - return; - } - - numBreakpoints_(): number { - return Object.keys(this.breakpoints).length; - } - - numListeners_(): number { - return Object.keys(this.listeners).length; - } - - private setInternal( - breakpoint: stackdriver.Breakpoint, - mapInfo: MapInfoOutput | null, - compile: ((src: string) => string) | null, - cb: (err: Error | null) => void - ): void { - // Parse and validate conditions and watch expressions for correctness and - // immutability - let ast = null; - if (breakpoint.condition) { - try { - // We parse as ES6; even though the underlying V8 version may only - // support a subset. This should be fine as the objective of the parse - // is to heuristically find side-effects. V8 will raise errors later - // if the syntax is invalid. It would have been nice if V8 had made the - // parser API available us :(. - ast = acorn.parse(breakpoint.condition, { - sourceType: 'script', - ecmaVersion: 6, - }); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const validator = require('../util/validator.js'); - if (!validator.isValid(ast)) { - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.BREAKPOINT_CONDITION, - utils.messages.DISALLOWED_EXPRESSION - ); - } - } catch (err) { - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.BREAKPOINT_CONDITION, - utils.messages.SYNTAX_ERROR_IN_CONDITION + (err as Error).message - ); - } - } - // Presently it is not possible to precisely disambiguate the script - // path from the path provided by the debug server. The issue is that we - // don't know the repository root relative to the root filesystem or - // relative to the working-directory of the process. We want to make sure - // that we are setting the breakpoint that the user intended instead of a - // breakpoint in a file that happens to have the same name but is in a - // different directory. Until this is addressed between the server and the - // debuglet, we are going to assume that repository root === the starting - // working directory. - let matchingScript; - const scriptPath = mapInfo - ? mapInfo.file - : path.normalize( - (breakpoint.location as stackdriver.SourceLocation).path - ); - const scripts = utils.findScripts( - scriptPath, - this.config, - this.fileStats, - this.logger - ); - if (scripts.length === 0) { - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.BREAKPOINT_SOURCE_LOCATION, - utils.messages.SOURCE_FILE_NOT_FOUND - ); - } else if (scripts.length === 1) { - // Found the script - matchingScript = scripts[0]; - } else { - this.logger.warn( - `Unable to unambiguously find ${scriptPath}. Potential matches: ${scripts}` - ); - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.BREAKPOINT_SOURCE_LOCATION, - utils.messages.SOURCE_FILE_AMBIGUOUS - ); - } - - // The breakpoint protobuf message presently doesn't have a column property - // but it may have one in the future. - // TODO: Address the case where `breakpoint.location` is `null`. - let column = - mapInfo && mapInfo.column - ? mapInfo.column - : (breakpoint.location as stackdriver.SourceLocation).column || 1; - const line = mapInfo - ? mapInfo.line - : (breakpoint.location as stackdriver.SourceLocation).line; - - // We need to special case breakpoints on the first line. Since Node.js - // wraps modules with a function expression, we adjust - // to deal with that. - if (line === 1) { - column += debugapi.MODULE_WRAP_PREFIX_LENGTH - 1; - } - - // TODO: Address the case where `fileStats[matchingScript]` is `null`. - if (line >= (this.fileStats[matchingScript] as FileStats).lines) { - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.BREAKPOINT_SOURCE_LOCATION, - utils.messages.INVALID_LINE_NUMBER + - matchingScript + - ':' + - line + - '. Loaded script contained ' + - (this.fileStats[matchingScript] as FileStats).lines + - ' lines. Please ensure' + - ' that the snapshot was set in the same code version as the' + - ' deployed source.' - ); - } - - const v8bp = this.setByRegExp(matchingScript, line, column); - if (!v8bp) { - return utils.setErrorStatusAndCallback( - cb, - breakpoint, - StatusMessage.BREAKPOINT_SOURCE_LOCATION, - utils.messages.V8_BREAKPOINT_ERROR - ); - } - if (this.numBreakpoints === 0 && !this.usePermanentListener) { - // added first breakpoint - this.logger.info('activating v8 breakpoint listener'); - this.v8.setListener(this.handleDebugEvents); - } - this.breakpoints[breakpoint.id] = - // TODO: Address the case where `ast` is `null`. - new V8BreakpointData(breakpoint, v8bp, ast as estree.Node, compile); - this.numBreakpoints++; - setImmediate(() => { - cb(null); - }); // success. - } - - private setByRegExp( - scriptPath: string, - line: number, - column: number - ): v8.BreakPoint { - const regexp = utils.pathToRegExp(scriptPath); - const num = this.v8.setScriptBreakPointByRegExp( - regexp, - line - 1, - column - 1 - ); - const v8bp = this.v8.findBreakPoint(num); - return v8bp; - } - - private onBreakpointHit( - breakpoint: stackdriver.Breakpoint, - callback: (err: Error | null) => void, - execState: v8.ExecutionState - ): void { - // TODO: Address the situation where `breakpoint.id` is `null`. - const v8bp = this.breakpoints[breakpoint.id].v8Breakpoint; - if (!v8bp.active()) { - // Breakpoint exists, but not active. We never disable breakpoints, so - // this is theoretically not possible. Perhaps this is possible if there - // is a second debugger present? Regardless, report the error. - return utils.setErrorStatusAndCallback( - callback, - breakpoint, - StatusMessage.BREAKPOINT_SOURCE_LOCATION, - utils.messages.V8_BREAKPOINT_DISABLED - ); - } - - const result = this.checkCondition(breakpoint, execState); - if (result.error) { - return utils.setErrorStatusAndCallback( - callback, - breakpoint, - StatusMessage.BREAKPOINT_CONDITION, - utils.messages.ERROR_EVALUATING_CONDITION + result.error - ); - } else if (!result.value) { - // Check again next time - this.logger.info("\tthe breakpoint condition wasn't met"); - return; - } - - // Breakpoint Hit - const start = process.hrtime(); - try { - this.captureBreakpointData(breakpoint, execState); - } catch (err) { - return utils.setErrorStatusAndCallback( - callback, - breakpoint, - StatusMessage.BREAKPOINT_SOURCE_LOCATION, - utils.messages.CAPTURE_BREAKPOINT_DATA + err - ); - } - const end = process.hrtime(start); - this.logger.info(utils.formatInterval('capture time: ', end)); - callback(null); - } - - /** - * Evaluates the breakpoint condition, if present. - * @return object with either a boolean value or an error property - */ - private checkCondition( - breakpoint: stackdriver.Breakpoint, - execState: v8.ExecutionState - ): {value?: boolean; error?: string} { - if (!breakpoint.condition) { - return {value: true}; - } - - const result = state.evaluate(breakpoint.condition, execState.frame(0)); - - if (result.error) { - return {error: result.error}; - } - // TODO: Address the case where `result.mirror` is `null`. - return { - value: !!(result.mirror as v8.ValueMirror).value(), - }; // intentional !! - } - - private captureBreakpointData( - breakpoint: stackdriver.Breakpoint, - execState: v8.ExecutionState - ): void { - const expressionErrors: Array = []; - if (breakpoint.expressions && this.breakpoints[breakpoint.id].compile) { - for (let i = 0; i < breakpoint.expressions.length; i++) { - try { - breakpoint.expressions[i] = - // TODO: Address the case where `compile` is `null`. - ( - this.breakpoints[breakpoint.id].compile as ( - text: string - ) => string - )(breakpoint.expressions[i]); - } catch (e) { - this.logger.info( - 'Unable to compile watch expression >> ' + - breakpoint.expressions[i] + - ' <<' - ); - expressionErrors.push({ - name: breakpoint.expressions[i], - status: new StatusMessage( - StatusMessage.VARIABLE_VALUE, - 'Error Compiling Expression', - true - ), - }); - breakpoint.expressions.splice(i, 1); - i--; - } - } - } - if (breakpoint.action === 'LOG') { - // TODO: This doesn't work with compiled languages if there is an error - // compiling one of the expressions in the loop above. - if (!breakpoint.expressions) { - breakpoint.evaluatedExpressions = []; - } else { - const frame = execState.frame(0); - const evaluatedExpressions = breakpoint.expressions.map(exp => { - const result = state.evaluate(exp, frame); - // TODO: Address the case where `result.mirror` is `undefined`. - return result.error - ? result.error - : (result.mirror as v8.ValueMirror).value(); - }); - breakpoint.evaluatedExpressions = evaluatedExpressions; - } - } else { - // TODO: Address the case where `breakpoint.expression` is `undefined`. - const captured = state.capture( - execState, - breakpoint.expressions as string[], - this.config, - this.v8 - ); - if ( - breakpoint.location && - utils.isJavaScriptFile(breakpoint.location.path) && - captured.location && - captured.location.line - ) { - breakpoint.location.line = captured.location.line; - } - breakpoint.stackFrames = captured.stackFrames; - // TODO: This suggests the Status type and Variable type are the same. - // Determine if that is the case. - breakpoint.variableTable = - captured.variableTable as stackdriver.Variable[]; - breakpoint.evaluatedExpressions = expressionErrors.concat( - captured.evaluatedExpressions - ); - } - } -} diff --git a/test/test-v8debugapi.ts b/test/test-v8debugapi.ts index 47c26e09..d907fbaf 100644 --- a/test/test-v8debugapi.ts +++ b/test/test-v8debugapi.ts @@ -126,42 +126,6 @@ function validateBreakpoint(breakpoint: stackdriver.Breakpoint): void { } } -describe('propertly determines if the inspector protocol should be used', () => { - let suffixes = ['', '.11', '.11.1']; - // also handle suffixes associated with nightly builds - suffixes = suffixes.concat( - suffixes.map(suffix => suffix + '-nightly201804132a6ab9b37b') - ); - - it('handles Node >=10 correctly', () => { - // on Node >= 10, inspector should always be used - for (let version = 10; version <= 11; version++) { - for (const suffix of suffixes) { - const fullVersion = `v${version}${suffix}`; - assert.strictEqual( - debugapi.willUseInspector(fullVersion), - true, - `Should use inspector in Node.js version ${fullVersion}` - ); - } - } - }); - - it('handles Node <10 correctly', () => { - // on Node < 10, inspector should never be used - for (let version = 4; version <= 9; version++) { - for (const suffix of suffixes) { - const fullVersion = `v${version}${suffix}`; - assert.strictEqual( - debugapi.willUseInspector(fullVersion), - false, - `Should not use inspector in Node.js version ${fullVersion}` - ); - } - } - }); -}); - describe('debugapi selection', () => { const config: ResolvedDebugAgentConfig = extend({}, defaultConfig, { workingDirectory: __dirname, @@ -188,15 +152,7 @@ describe('debugapi selection', () => { jsStats, mapper as SourceMapper.SourceMapper ) as DebugApi; - if (debugapi.willUseInspector()) { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const inspectorapi = require('../src/agent/v8/inspector-debugapi'); - assert.ok(api instanceof inspectorapi.InspectorDebugApi); - } else { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const v8debugapi = require('../src/agent/v8/legacy-debugapi'); - assert.ok(api instanceof v8debugapi.V8DebugApi); - } + assert.ok(api instanceof InspectorDebugApi); done(); }); }); From c5bbb4ead353614a548e1f962c9fdf3cc6ea9845 Mon Sep 17 00:00:00 2001 From: James McTavish Date: Wed, 31 May 2023 13:01:09 -0400 Subject: [PATCH 06/10] fix: unblock compodoc page generation (#1145) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 0e946686..cedb7125 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ } }, "devDependencies": { + "@babel/plugin-proposal-private-methods": "^7.18.6", "@compodoc/compodoc": "1.1.21", "@types/acorn": "^4.0.2", "@types/console-log-level": "^1.4.0", From c292200ff83660d7f9c509af2c45c5e6a1565011 Mon Sep 17 00:00:00 2001 From: James McTavish Date: Mon, 5 Jun 2023 14:34:35 -0400 Subject: [PATCH 07/10] feat: add stop function to allow shutting down the debug agent (#1147) --- src/agent/debuglet.ts | 35 +++++++++++++-- src/agent/firebase-controller.ts | 2 +- src/index.ts | 13 +++++- .../fixtures/sample/src/allowExpressions.ts | 1 + .../fixtures/sample/src/allowExpressionsJs.js | 1 + .../sample/src/completeServiceContext.ts | 1 + system-test/fixtures/sample/src/firebase.ts | 16 +++++++ system-test/fixtures/sample/src/import.ts | 1 + system-test/fixtures/sample/src/noargs.ts | 1 + .../fixtures/sample/src/partialCapture.ts | 1 + .../sample/src/partialServiceContext.ts | 1 + system-test/fixtures/sample/src/start.js | 1 + system-test/fixtures/sample/src/startEmpty.js | 1 + test/test-debuglet.ts | 43 +++++++++++++++++++ 14 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 system-test/fixtures/sample/src/firebase.ts diff --git a/src/agent/debuglet.ts b/src/agent/debuglet.ts index 255f337e..328ad47e 100644 --- a/src/agent/debuglet.ts +++ b/src/agent/debuglet.ts @@ -187,6 +187,7 @@ export interface FindFilesResult { export class Debuglet extends EventEmitter { private debug: Debug; private v8debug: DebugApi | null; + private started: boolean; private running: boolean; private project: string | null; private controller: Controller | null; @@ -241,6 +242,9 @@ export class Debuglet extends EventEmitter { */ this.v8debug = null; + /** @private {boolean} */ + this.started = false; + /** @private {boolean} */ this.running = false; @@ -351,6 +355,7 @@ export class Debuglet extends EventEmitter { * @private */ async start(): Promise { + this.started = true; const stat = util.promisify(fs.stat); try { @@ -734,6 +739,10 @@ export class Debuglet extends EventEmitter { } setTimeout(() => { + if (!this.running) { + this.logger.info('Debuglet is stopped; not registering'); + return; + } assert(that.controller); if (!that.running) { onError(new Error('Debuglet not running')); @@ -785,6 +794,10 @@ export class Debuglet extends EventEmitter { } startListeningForBreakpoints_(): void { + if (!this.running) { + this.logger.info('Debuglet is stopped; not listening for breakpoints'); + return; + } assert(this.controller); // TODO: Handle the case where this.debuggee is null or not properly registered. this.controller.subscribeToBreakpoints( @@ -1072,16 +1085,30 @@ export class Debuglet extends EventEmitter { } /** - * Stops the Debuglet. This is for testing purposes only. Stop should only be - * called on a agent that has started (i.e. emitted the 'started' event). - * Calling this while the agent is initializing may not necessarily stop all - * pending operations. + * Stops the Debuglet. + * + * Stop should only be called on a agent that has started. */ stop(): void { + if (this.running) { + this.stopController(); + } else { + if (!this.started) { + this.logger.info('Attempt to stop Debuglet before it was started'); + return; + } + this.on('started', () => { + this.stopController(); + }); + } + } + + stopController(): void { assert(this.controller); assert.ok(this.running, 'stop can only be called on a running agent'); this.logger.debug('Stopping Debuglet'); this.running = false; + this.started = false; this.controller.stop(); this.emit('stopped'); } diff --git a/src/agent/firebase-controller.ts b/src/agent/firebase-controller.ts index 455b43d9..4e0e61aa 100644 --- a/src/agent/firebase-controller.ts +++ b/src/agent/firebase-controller.ts @@ -349,7 +349,7 @@ export class FirebaseController implements Controller { debuglog(`starting to mark every ${this.markActivePeriodMsec} ms`); this.markActiveInterval = setInterval(() => { this.markDebuggeeActive(); - }, this.markActivePeriodMsec); + }, this.markActivePeriodMsec).unref(); } /** diff --git a/src/index.ts b/src/index.ts index 8d2c0858..97697fa7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,7 @@ import * as util from 'util'; const debuglog = util.debuglog('cdbg'); // Singleton. -let debuglet: Debuglet; +let debuglet: Debuglet | undefined; /** * Start the Debug agent that will make your application available for debugging @@ -86,3 +86,14 @@ function mergeConfigs(options: T & {debug?: T}): T { export function get(): Debuglet | undefined { return debuglet; } + +/** + * Cleanly shut down the debug agent. + * + * This will free up all resources. It may be necessary to call this to allow + * your process to shut down cleanly. + */ +export function stop(): void { + debuglet?.stop(); + debuglet = undefined; +} diff --git a/system-test/fixtures/sample/src/allowExpressions.ts b/system-test/fixtures/sample/src/allowExpressions.ts index 70bc9eb1..825b2a0b 100644 --- a/system-test/fixtures/sample/src/allowExpressions.ts +++ b/system-test/fixtures/sample/src/allowExpressions.ts @@ -13,3 +13,4 @@ import * as debug from '@google-cloud/debug-agent'; debug.start({allowExpressions: true}); +debug.stop(); diff --git a/system-test/fixtures/sample/src/allowExpressionsJs.js b/system-test/fixtures/sample/src/allowExpressionsJs.js index 3623336b..63a5f8ce 100644 --- a/system-test/fixtures/sample/src/allowExpressionsJs.js +++ b/system-test/fixtures/sample/src/allowExpressionsJs.js @@ -14,3 +14,4 @@ require('@google-cloud/debug-agent').start({ allowExpressions: true, }); +require('@google-cloud/debug-agent').stop(); diff --git a/system-test/fixtures/sample/src/completeServiceContext.ts b/system-test/fixtures/sample/src/completeServiceContext.ts index 76a4a722..980837d6 100644 --- a/system-test/fixtures/sample/src/completeServiceContext.ts +++ b/system-test/fixtures/sample/src/completeServiceContext.ts @@ -19,3 +19,4 @@ debug.start({ version: 'Some version', }, }); +debug.stop(); diff --git a/system-test/fixtures/sample/src/firebase.ts b/system-test/fixtures/sample/src/firebase.ts new file mode 100644 index 00000000..daefcd7d --- /dev/null +++ b/system-test/fixtures/sample/src/firebase.ts @@ -0,0 +1,16 @@ +// Copyright 2023 Google LLC +// 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 +// +// https://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 debug from '@google-cloud/debug-agent'; +debug.start({useFirebase: true}); +debug.stop(); diff --git a/system-test/fixtures/sample/src/import.ts b/system-test/fixtures/sample/src/import.ts index 6bd50d01..73717ee4 100644 --- a/system-test/fixtures/sample/src/import.ts +++ b/system-test/fixtures/sample/src/import.ts @@ -13,3 +13,4 @@ import * as debug from '@google-cloud/debug-agent'; debug.start(); +debug.stop(); diff --git a/system-test/fixtures/sample/src/noargs.ts b/system-test/fixtures/sample/src/noargs.ts index 6bd50d01..73717ee4 100644 --- a/system-test/fixtures/sample/src/noargs.ts +++ b/system-test/fixtures/sample/src/noargs.ts @@ -13,3 +13,4 @@ import * as debug from '@google-cloud/debug-agent'; debug.start(); +debug.stop(); diff --git a/system-test/fixtures/sample/src/partialCapture.ts b/system-test/fixtures/sample/src/partialCapture.ts index 54b45dca..034045bc 100644 --- a/system-test/fixtures/sample/src/partialCapture.ts +++ b/system-test/fixtures/sample/src/partialCapture.ts @@ -17,3 +17,4 @@ debug.start({ maxFrames: 1, }, }); +debug.stop(); diff --git a/system-test/fixtures/sample/src/partialServiceContext.ts b/system-test/fixtures/sample/src/partialServiceContext.ts index bb9480c7..10202e2f 100644 --- a/system-test/fixtures/sample/src/partialServiceContext.ts +++ b/system-test/fixtures/sample/src/partialServiceContext.ts @@ -18,3 +18,4 @@ debug.start({ service: 'Some service', }, }); +debug.stop(); diff --git a/system-test/fixtures/sample/src/start.js b/system-test/fixtures/sample/src/start.js index eb43d997..3c25ac83 100644 --- a/system-test/fixtures/sample/src/start.js +++ b/system-test/fixtures/sample/src/start.js @@ -12,3 +12,4 @@ // limitations under the License. require('@google-cloud/debug-agent').start(); +require('@google-cloud/debug-agent').stop(); diff --git a/system-test/fixtures/sample/src/startEmpty.js b/system-test/fixtures/sample/src/startEmpty.js index a4ae5ba3..99333013 100644 --- a/system-test/fixtures/sample/src/startEmpty.js +++ b/system-test/fixtures/sample/src/startEmpty.js @@ -12,3 +12,4 @@ // limitations under the License. require('@google-cloud/debug-agent').start({}); +require('@google-cloud/debug-agent').stop(); diff --git a/test/test-debuglet.ts b/test/test-debuglet.ts index c8b7f152..edb207c1 100644 --- a/test/test-debuglet.ts +++ b/test/test-debuglet.ts @@ -840,6 +840,49 @@ describe('Debuglet', () => { debuglet.start(); }); + it('should stop successfully even if stop is called quickly', () => { + const debug = new Debug( + {projectId: 'fake-project', credentials: fakeCredentials}, + packageInfo + ); + + nocks.oauth2(); + + const config = debugletConfig(); + const debuglet = new Debuglet(debug, config); + + debuglet.start(); + debuglet.stop(); + }); + + it('should register successfully even if stop was called first', done => { + const debug = new Debug( + {projectId: 'fake-project', credentials: fakeCredentials}, + packageInfo + ); + + nocks.oauth2(); + + const config = debugletConfig(); + const debuglet = new Debuglet(debug, config); + const scope = nock(config.apiUrl) + .post(REGISTER_PATH) + .reply(200, { + debuggee: {id: DEBUGGEE_ID}, + }); + + debuglet.once('registered', (id: string) => { + assert.strictEqual(id, DEBUGGEE_ID); + debuglet.stop(); + scope.done(); + done(); + }); + + debuglet.stop(); + + debuglet.start(); + }); + it('should attempt to retrieve cluster name if needed', done => { const savedRunningOnGCP = Debuglet.runningOnGCP; Debuglet.runningOnGCP = () => { From 7ed8fb2a16bea36903fbc72ebc066b19bb10143c Mon Sep 17 00:00:00 2001 From: James McTavish Date: Mon, 5 Jun 2023 15:38:04 -0400 Subject: [PATCH 08/10] feat!: Remove support for deprecated Cloud Debugger API. (#1144) After this change, all agents will use the firebase backend. --- src/agent/debuglet.ts | 78 +- src/agent/firebase-controller.ts | 5 +- src/agent/oneplatform-controller.ts | 328 --- src/client/stackdriver/debug.ts | 90 - src/debuggee.ts | 2 +- src/index.ts | 12 +- system-test/test-controller.ts | 27 +- system-test/test-e2e.ts | 650 ++--- test/debugger.ts | 248 +- test/test-controller.ts | 709 +++--- test/test-debuglet.ts | 3440 +++++++++++++-------------- test/test-module.ts | 80 +- test/test-options-credentials.ts | 225 +- 13 files changed, 2610 insertions(+), 3284 deletions(-) delete mode 100644 src/agent/oneplatform-controller.ts delete mode 100644 src/client/stackdriver/debug.ts diff --git a/src/agent/debuglet.ts b/src/agent/debuglet.ts index 328ad47e..856a57c6 100644 --- a/src/agent/debuglet.ts +++ b/src/agent/debuglet.ts @@ -22,7 +22,6 @@ import * as metadata from 'gcp-metadata'; import * as path from 'path'; import * as util from 'util'; -import {Debug, PackageInfo} from '../client/stackdriver/debug'; import {StatusMessage} from '../client/stackdriver/status-message'; import {CanaryMode, Debuggee, DebuggeeProperties} from '../debuggee'; import * as stackdriver from '../types/stackdriver'; @@ -35,7 +34,6 @@ import { ResolvedDebugAgentConfig, } from './config'; import {Controller} from './controller'; -import {OnePlatformController} from './oneplatform-controller'; import * as scanner from './io/scanner'; import * as SourceMapper from './io/sourcemapper'; import * as utils from './util/utils'; @@ -79,6 +77,11 @@ export enum Platforms { DEFAULT = 'default', } +export interface PackageInfo { + name: string; + version: string; +} + /** * Formats a breakpoint object prefixed with a provided message as a string * intended for logging. @@ -185,7 +188,7 @@ export interface FindFilesResult { } export class Debuglet extends EventEmitter { - private debug: Debug; + private packageInfo: PackageInfo; private v8debug: DebugApi | null; private started: boolean; private running: boolean; @@ -216,7 +219,7 @@ export class Debuglet extends EventEmitter { activeBreakpointMap: {[key: string]: stackdriver.Breakpoint}; /** - * @param {Debug} debug - A Debug instance. + * @param {PackageInfo} packageInfo - Information about the agent package. * @param {object=} config - The option parameters for the Debuglet. * @event 'started' once the startup tasks are completed. Only called once. * @event 'stopped' if the agent stops due to a fatal error after starting. @@ -227,14 +230,14 @@ export class Debuglet extends EventEmitter { * called multiple times. * @constructor */ - constructor(debug: Debug, config: DebugAgentConfig) { + constructor(packageInfo: PackageInfo, config: DebugAgentConfig) { super(); /** @private {object} */ this.config = Debuglet.normalizeConfig_(config); - /** @private {Debug} */ - this.debug = debug; + /** @private {PackageInfo} */ + this.packageInfo = packageInfo; /** * @private {object} V8 Debug API. This can be null if the Node.js version @@ -257,7 +260,7 @@ export class Debuglet extends EventEmitter { /** @private */ this.logger = consoleLogLevel({ stderr: true, - prefix: this.debug.packageInfo.name, + prefix: this.packageInfo.name, level: Debuglet.logLevelToName(this.config.logLevel), }); @@ -431,37 +434,20 @@ export class Debuglet extends EventEmitter { } let project: string; - if (this.config.useFirebase) { - try { - const firebaseDb = await FirebaseController.initialize({ - keyPath: this.config.firebaseKeyPath, - databaseUrl: this.config.firebaseDbUrl, - projectId: this.config.projectId, - }); - this.controller = new FirebaseController(firebaseDb); - project = (this.controller as FirebaseController).getProjectId(); - } catch (err) { - this.logger.error( - 'Unable to connect to Firebase: ' + (err as Error).message - ); - this.emit('initError', err); - return; - } - } else { - try { - project = await this.debug.authClient.getProjectId(); - } catch (err) { - this.logger.error( - 'The project ID could not be determined: ' + (err as Error).message - ); - this.emit('initError', err); - return; - } - this.controller = new OnePlatformController( - this.debug, - this.config, - this.logger + try { + const firebaseDb = await FirebaseController.initialize({ + keyPath: this.config.firebaseKeyPath, + databaseUrl: this.config.firebaseDbUrl, + projectId: this.config.projectId, + }); + this.controller = new FirebaseController(firebaseDb); + project = (this.controller as FirebaseController).getProjectId(); + } catch (err) { + this.logger.error( + 'Unable to connect to Firebase: ' + (err as Error).message ); + this.emit('initError', err); + return; } if ( @@ -518,7 +504,7 @@ export class Debuglet extends EventEmitter { this.config.serviceContext, sourceContext, onGCP, - this.debug.packageInfo, + this.packageInfo, platform, this.config.description, /*errorMessage=*/ undefined, @@ -861,7 +847,7 @@ export class Debuglet extends EventEmitter { // New breakpoint this.addBreakpoint_(breakpoint, err => { if (err) { - this.completeBreakpoint_(breakpoint, false); + this.completeBreakpoint_(breakpoint); } }); @@ -1011,10 +997,7 @@ export class Debuglet extends EventEmitter { * @param {Breakpoint} breakpoint * @private */ - completeBreakpoint_( - breakpoint: stackdriver.Breakpoint, - deleteFromV8 = true - ): void { + completeBreakpoint_(breakpoint: stackdriver.Breakpoint): void { assert(this.controller); this.logger.info('\tupdating breakpoint data on server', breakpoint.id); @@ -1027,13 +1010,6 @@ export class Debuglet extends EventEmitter { this.logger.error('Unable to complete breakpoint on server', err); return; } - // The Firebase controller will remove the breakpoint during the update - // by removing it from the database. - if (!this.config.useFirebase) { - // TODO: Address the case when `breakpoint.id` is `undefined`. - this.completedBreakpointMap[breakpoint.id as string] = true; - this.removeBreakpoint_(breakpoint, deleteFromV8); - } } ); } diff --git a/src/agent/firebase-controller.ts b/src/agent/firebase-controller.ts index 4e0e61aa..a03d49fd 100644 --- a/src/agent/firebase-controller.ts +++ b/src/agent/firebase-controller.ts @@ -76,8 +76,9 @@ export class FirebaseController implements Controller { credential = firebase.credential.cert(serviceAccount); } else { if (!projectId) { - // Try grabbing it from the GCE metadata server. - if (await gcpMetadata.isAvailable()) { + if (process.env.GCLOUD_PROJECT) { + projectId = process.env.GCLOUD_PROJECT; + } else if (await gcpMetadata.isAvailable()) { projectId = await gcpMetadata.project('project-id'); } } diff --git a/src/agent/oneplatform-controller.ts b/src/agent/oneplatform-controller.ts deleted file mode 100644 index 34ae43de..00000000 --- a/src/agent/oneplatform-controller.ts +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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. - -/*! - * @module debug/oneplatform-controller - */ - -import {ServiceObject} from '@google-cloud/common'; -import * as assert from 'assert'; -import * as qs from 'querystring'; -import * as t from 'teeny-request'; - -import {URL} from 'url'; - -import {Logger, ResolvedDebugAgentConfig} from './config'; -import {Controller} from './controller'; -import {StatusMessage} from '../client/stackdriver/status-message'; -import {Debug} from '../client/stackdriver/debug'; -import {Debuggee} from '../debuggee'; -import * as stackdriver from '../types/stackdriver'; - -const BREAKPOINT_ACTION_MESSAGE = - 'The only currently supported breakpoint actions' + ' are CAPTURE and LOG.'; - -export class OnePlatformController extends ServiceObject implements Controller { - private nextWaitToken: string | null; - private agentId: string | null; - private config: ResolvedDebugAgentConfig; - private fetcherActive: boolean; - private running: boolean; - - apiUrl: string; - - logger: Logger; - - /** - * @constructor - */ - constructor(debug: Debug, config: ResolvedDebugAgentConfig, logger: Logger) { - super({parent: debug, baseUrl: '/controller'}); - - /** @private {string} */ - this.nextWaitToken = null; - this.agentId = null; - - this.apiUrl = `https://${debug.apiEndpoint}/v2/controller`; - - this.fetcherActive = false; - this.running = true; - - /** @private */ - this.logger = logger; - - if (config && config.apiUrl) { - this.apiUrl = config.apiUrl + new URL(this.apiUrl).pathname; - } - - this.config = config; - } - - /** - * Register to the API (implementation) - * - * @param {!function(?Error,Object=)} callback - * @private - */ - register( - debuggee: Debuggee, - callback: ( - err: Error | null, - result?: { - debuggee: Debuggee; - agentId: string; - } - ) => void - ): void { - const options = { - uri: this.apiUrl + '/debuggees/register', - method: 'POST', - json: true, - body: {debuggee}, - }; - this.request( - options, - (err, body: {debuggee: Debuggee; agentId: string}, response) => { - if (err) { - callback(err); - } else if (response!.statusCode !== 200) { - callback( - new Error('unable to register, statusCode ' + response!.statusCode) - ); - } else if (!body.debuggee) { - callback(new Error('invalid response body from server')); - } else { - debuggee.id = body.debuggee.id; - this.agentId = body.agentId; - callback(null, body); - } - } - ); - } - - /** - * Fetch the list of breakpoints from the server. Assumes we have registered. - * @param {!function(?Error,Object=,Object=)} callback accepting (err, response, - * body) - */ - listBreakpoints( - debuggee: Debuggee, - callback: ( - err: Error | null, - response?: t.Response, - body?: stackdriver.ListBreakpointsResponse - ) => void - ): void { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const that = this; - assert(debuggee.id, 'should have a registered debuggee'); - const query: stackdriver.ListBreakpointsQuery = {successOnTimeout: true}; - if (that.nextWaitToken) { - query.waitToken = that.nextWaitToken; - } - if (that.agentId) { - query.agentId = that.agentId; - } - - const uri = - this.apiUrl + - '/debuggees/' + - encodeURIComponent(debuggee.id) + - '/breakpoints?' + - qs.stringify(query as qs.ParsedUrlQueryInput); - that.request( - {uri, json: true}, - (err, body: stackdriver.ListBreakpointsResponse, response) => { - if (!response) { - callback( - err || new Error('unknown error - request response missing') - ); - return; - } else if (response.statusCode === 404) { - // The v2 API returns 404 (google.rpc.Code.NOT_FOUND) when the agent - // registration expires. We should re-register. - callback(null, response as {} as t.Response); - return; - } else if (response.statusCode !== 200) { - callback( - new Error( - 'unable to list breakpoints, status code ' + response.statusCode - ) - ); - return; - } else { - body = body || {}; - that.nextWaitToken = body.nextWaitToken; - callback(null, response as {} as t.Response, body); - } - } - ); - } - - /** - * Update the server about breakpoint state - * @param {!Debuggee} debuggee - * @param {!Breakpoint} breakpoint - * @param {!Function} callback accepting (err, body) - */ - updateBreakpoint( - debuggee: Debuggee, - breakpoint: stackdriver.Breakpoint, - callback: (err?: Error, body?: {}) => void - ): void { - assert(debuggee.id, 'should have a registered debuggee'); - - breakpoint.action = 'CAPTURE'; - breakpoint.isFinalState = true; - const options = { - uri: - this.apiUrl + - '/debuggees/' + - encodeURIComponent(debuggee.id) + - // TODO: Address the case where `breakpoint.id` is `undefined`. - '/breakpoints/' + - encodeURIComponent(breakpoint.id as string), - json: true, - method: 'PUT', - body: {debuggeeId: debuggee.id, breakpoint}, - }; - - // We need to have a try/catch here because a JSON.stringify will be done - // by request. Some V8 debug mirror objects get a throw when we attempt to - // stringify them. The try-catch keeps it resilient and avoids crashing the - // user's app. - try { - this.request(options, (err, body /*, response */) => { - callback(err!, body); - }); - } catch (error) { - callback(error as Error); - } - } - - subscribeToBreakpoints( - debuggee: Debuggee, - callback: (err: Error | null, breakpoints: stackdriver.Breakpoint[]) => void - ): void { - if (!this.fetcherActive) { - this.scheduleBreakpointFetch_(debuggee, 0, false, callback); - } - } - - scheduleBreakpointFetch_( - debuggee: Debuggee, - seconds: number, - once: boolean, - callback: (err: Error | null, breakpoints: stackdriver.Breakpoint[]) => void - ): void { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const that = this; - if (!once) { - that.fetcherActive = true; - } - setTimeout(() => { - if (!that.running) { - return; - } - - that.logger.info('Fetching breakpoints'); - if (!once) { - that.fetcherActive = true; - } - // TODO: Address the case when `that.debuggee` is `null`. - that.listBreakpoints(debuggee, (err, response, body) => { - if (err) { - that.logger.error( - 'Error fetching breakpoints – scheduling retry', - err - ); - // Return the error, prompting a re-registration. - that.fetcherActive = false; - callback(err, []); - return; - } - switch (response!.statusCode) { - case 404: { - // Registration expired. Deactivate the fetcher and queue - // re-registration, which will re-active breakpoint fetching. - that.logger.info('\t404 Registration expired.'); - that.fetcherActive = false; - const expiredError = new Error(response!.statusMessage); - expiredError.name = 'RegistrationExpiredError'; - callback(expiredError, []); - return; - } - - default: - // TODO: Address the case where `response` is `undefined`. - that.logger.info('\t' + response!.statusCode + ' completed.'); - if (!body) { - that.logger.error('\tinvalid list response: empty body'); - that.scheduleBreakpointFetch_( - debuggee, - that.config.breakpointUpdateIntervalSec, - once, - callback - ); - return; - } - if (body.waitExpired) { - that.logger.info('\tLong poll completed.'); - that.scheduleBreakpointFetch_(debuggee, 0, once, callback); - return; - } - // eslint-disable-next-line no-case-declarations - const bps = (body.breakpoints || []).filter( - (bp: stackdriver.Breakpoint) => { - const action = bp.action || 'CAPTURE'; - if (action !== 'CAPTURE' && action !== 'LOG') { - that.logger.warn( - 'Found breakpoint with invalid action:', - action - ); - bp.status = new StatusMessage( - StatusMessage.UNSPECIFIED, - BREAKPOINT_ACTION_MESSAGE, - true - ); - that.updateBreakpoint(debuggee, bp, (err /*, body*/) => { - if (err) { - that.logger.error( - 'Unable to complete breakpoint on server', - err - ); - } - }); - return false; - } - return true; - } - ); - callback(null, bps); - that.scheduleBreakpointFetch_( - debuggee, - that.config.breakpointUpdateIntervalSec, - once, - callback - ); - } - return; - }); - }, seconds * 1000).unref(); - } - - stop(): void { - this.running = false; - } -} diff --git a/src/client/stackdriver/debug.ts b/src/client/stackdriver/debug.ts deleted file mode 100644 index d8f4b5d0..00000000 --- a/src/client/stackdriver/debug.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2015 Google LLC -// -// 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 {GoogleAuthOptions, Service} from '@google-cloud/common'; - -export interface PackageInfo { - name: string; - version: string; -} - -export interface DebugOptions extends GoogleAuthOptions { - /** - * The API endpoint of the service used to make requests. - * Defaults to `clouddebugger.googleapis.com`. - */ - apiEndpoint?: string; -} - -export class Debug extends Service { - options!: DebugOptions; - packageInfo!: PackageInfo; - - /** - *

- * **This is an experimental release of Stackdriver Debug.** This API is not - * covered by any SLA of deprecation policy and may be subject to backwards - * incompatible changes. - *

- * - * This module provides Stackdriver Debugger support for Node.js applications. - * [Stackdriver Debugger](https://cloud.google.com/debug/) is a feature of - * [Google Cloud Platform](https://cloud.google.com/) that lets you debug your - * applications in production without stopping or pausing your application. - * - * This module provides an agent that lets you automatically enable debugging - * without changes to your application. - * - * @constructor - * @alias module:debug - * - * @resource [What is Stackdriver Debug]{@link - * https://cloud.google.com/debug/} - * - * @param options - [Authentication options](#/docs) - */ - constructor( - options: DebugOptions = {}, - packageJson: { - name: string; - version: string; - } - ) { - if (new.target !== Debug) { - return new Debug(options, packageJson); - } - options.apiEndpoint = options.apiEndpoint || 'clouddebugger.googleapis.com'; - const config = { - projectIdRequired: false, - apiEndpoint: options.apiEndpoint, - baseUrl: `https://${options.apiEndpoint}/v2`, - scopes: ['https://www.googleapis.com/auth/cloud_debugger'], - packageJson, - }; - - // TODO: Update Service to provide types - // TODO: Determine if we should check if `options` is `undefined` or - // `null` here and, if so, provide a default value. - super(config, options); - - // FIXME(ofrobots): We need our own copy of options because Service may - // default to '{{projectId}}' when options doesn't contain the `projectId`. - // property. This breaks the SSOT principle. Remove this when - // https://github.com/googleapis/google-cloud-node/issues/1891 - // is resolved. - this.options = options; - - this.packageInfo = {name: packageJson.name, version: packageJson.version}; - } -} diff --git a/src/debuggee.ts b/src/debuggee.ts index 1cecb1c3..680df0ca 100644 --- a/src/debuggee.ts +++ b/src/debuggee.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {PackageInfo} from './client/stackdriver/debug'; +import {PackageInfo} from './agent/debuglet'; import {StatusMessage} from './client/stackdriver/status-message'; export declare type CanaryMode = diff --git a/src/index.ts b/src/index.ts index 97697fa7..4288cbdf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,6 @@ import {DebugAgentConfig, StackdriverConfig} from './agent/config'; import {Debuglet, IsReady} from './agent/debuglet'; -import {Debug} from './client/stackdriver/debug'; // eslint-disable-next-line @typescript-eslint/no-var-requires const pjson = require('../../package.json'); @@ -36,7 +35,7 @@ let debuglet: Debuglet | undefined; * * @example * ``` - * debug.startAgent(); + * debug.start(); * ``` */ export function start( @@ -50,13 +49,8 @@ export function start( throw new Error('Debug Agent has already been started'); } - if (agentConfig.useFirebase) { - debuglog('Running with experimental firebase backend.'); - debuglet = new Debuglet({packageInfo: pjson} as Debug, agentConfig); - } else { - const debug = new Debug(options, pjson); - debuglet = new Debuglet(debug, agentConfig); - } + debuglog('Running with firebase backend.'); + debuglet = new Debuglet(pjson, agentConfig); debuglet.start(); diff --git a/system-test/test-controller.ts b/system-test/test-controller.ts index 4ee11905..4405766a 100644 --- a/system-test/test-controller.ts +++ b/system-test/test-controller.ts @@ -13,7 +13,7 @@ // limitations under the License. import * as assert from 'assert'; -import {describe, it} from 'mocha'; +// import {describe, it} from 'mocha'; assert.ok( process.env.GCLOUD_PROJECT, @@ -25,20 +25,20 @@ assert.ok( 'this test' ); -import * as stackdriver from '../src/types/stackdriver'; -import {OnePlatformController} from '../src/agent/oneplatform-controller'; -import {Debuggee} from '../src/debuggee'; -import {Debug, PackageInfo} from '../src/client/stackdriver/debug'; -import {defaultConfig as DEFAULT_CONFIG} from '../src/agent/config'; -import {MockLogger} from '../test/mock-logger'; +// import * as stackdriver from '../src/types/stackdriver'; +// import {Debuggee} from '../src/debuggee'; +// import {PackageInfo} from '../src/agent/debuglet'; +// import {defaultConfig as DEFAULT_CONFIG} from '../src/agent/config'; +// import {MockLogger} from '../test/mock-logger'; -const packageInfo: PackageInfo = { - name: 'SomeName', - version: 'SomeVersion', -}; -const agentVersion = `${packageInfo.name}/client/${packageInfo.version}`; +// const packageInfo: PackageInfo = { +// name: 'SomeName', +// version: 'SomeVersion', +// }; +// const agentVersion = `${packageInfo.name}/client/${packageInfo.version}`; -const debug = new Debug({}, packageInfo); +/* +TODO: Write tests that work for Firebase backend, if they aren't covered by E2E. describe('Controller', function () { this.timeout(60 * 1000); @@ -126,3 +126,4 @@ describe('Controller', function () { // breakpoint (need the debugger API). TODO. it('should update an active breakpoint'); }); +*/ diff --git a/system-test/test-e2e.ts b/system-test/test-e2e.ts index d58b7984..ac17fab7 100644 --- a/system-test/test-e2e.ts +++ b/system-test/test-e2e.ts @@ -12,328 +12,328 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as assert from 'assert'; -import {afterEach, before, beforeEach, describe, it} from 'mocha'; -import * as cp from 'child_process'; -import * as util from 'util'; -import * as uuid from 'uuid'; - -import {Debug} from '../src/client/stackdriver/debug'; -import * as stackdriver from '../src/types/stackdriver'; -import {Debugger} from '../test/debugger'; - -const CLUSTER_WORKERS = 3; - -const FILENAME = 'build/test/fixtures/fib.js'; - -const UUID = uuid.v4(); -const LOG_MESSAGE_FORMAT = UUID + ': o is: $0'; - -// fib.js uses a custom log-point function that lower cases everything. -const REGEX = new RegExp( - `logpoint: ${UUID}: o is: {"a":\\[1,"hi",true\\]}`, - 'g' -); - -const delay = (delayTimeMS: number): Promise => { - return new Promise(r => setTimeout(r, delayTimeMS)); -}; - -interface Child { - transcript: string; - process?: cp.ChildProcess; -} - -// This test could take up to 100 seconds. -describe('@google-cloud/debug end-to-end behavior', () => { - let api: Debugger; - - let debuggeeId: string | null; - let projectId: string | null; - let children: Child[] = []; - - before(() => { - const packageInfo = {name: 'Some name', version: 'Some version'}; - api = new Debugger(new Debug({}, packageInfo)); - }); - - beforeEach(function () { - this.timeout(10 * 1000); - return new Promise((resolve, reject) => { - let numChildrenReady = 0; - - // Process a status message sent from a child process. - const handler = (c: { - error: Error | null; - debuggeeId: string; - projectId: string; - }) => { - console.log('handler received:', c); - if (c.error) { - reject(new Error('A child reported the following error: ' + c.error)); - return; - } - if (!debuggeeId) { - // Cache the needed info from the first worker. - debuggeeId = c.debuggeeId; - projectId = c.projectId; - } else { - // Make sure all other workers are consistent. - if (debuggeeId !== c.debuggeeId || projectId !== c.projectId) { - reject( - new Error( - 'Child debuggee ID and/or project ID' + - 'is not consistent with previous child' - ) - ); - return; - } - } - numChildrenReady++; - if (numChildrenReady === CLUSTER_WORKERS) { - console.log('All children are ready'); - resolve(); - } - }; - - // Handle stdout/stderr output from a child process. More specifically, - // write the child process's output to a transcript. - // Each child has its own transcript. - const stdoutHandler = (index: number) => { - return (chunk: string) => { - children[index].transcript += chunk; - }; - }; - - for (let i = 0; i < CLUSTER_WORKERS; i++) { - // Fork child processes that send messages to this process with IPC. - // We pass UUID to the children so that they can all get the same - // debuggee id. - const child: Child = {transcript: ''}; - child.process = cp.fork(FILENAME, /* args */ [UUID], { - execArgv: [], - env: process.env, - silent: true, - }); - child.process.on('message', handler); - - children.push(child); - child.process.stdout!.on('data', stdoutHandler(i)); - child.process.stderr!.on('data', stdoutHandler(i)); - } - }); - }); - - afterEach(function () { - this.timeout(5 * 1000); - // Create a promise for each child that resolves when that child exits. - const childExitPromises = children.map((child, index) => { - console.log(`child ${index} transcript: ===#`, child.transcript, '#==='); - assert(child.process); - const childProcess = child.process as cp.ChildProcess; - childProcess.kill(); - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error('A child process failed to exit.')); - }, 3000); - childProcess.on('exit', () => { - clearTimeout(timeout); - resolve(); - }); - }); - }); - // Wait until all children exit, then reset test state. - return Promise.all(childExitPromises).then(() => { - debuggeeId = null; - projectId = null; - children = []; - }); - }); - - async function verifyDebuggeeFound() { - const allDebuggees = await api.listDebuggees(projectId!, true); - const debuggees = allDebuggees.filter( - debuggee => debuggee.isInactive !== true - ); - - // Check that the debuggee created in this test is among the list of - // debuggees, then list its breakpoints - console.log( - '-- List of debuggees\n', - util.inspect(debuggees, {depth: null}) - ); - assert.ok(debuggees, 'should get a valid ListDebuggees response'); - - const result = debuggees.find(d => d.id === debuggeeId); - assert.ok( - result, - `should find the debuggee we just registered, expected debuggeeId: ${debuggeeId}, found: ${result}` - ); - } - - async function verifyDeleteBreakpoints() { - // Delete every breakpoint - const breakpoints = await api.listBreakpoints(debuggeeId!, {}); - console.log('-- List of breakpoints\n', breakpoints); - - const promises = breakpoints.map(breakpoint => { - return api.deleteBreakpoint(debuggeeId!, breakpoint.id); - }); - await Promise.all(promises); - - const breakpointsAfterDelete = await api.listBreakpoints(debuggeeId!, {}); - assert.strictEqual(breakpointsAfterDelete.length, 0); - console.log('-- deleted'); - } - - async function verifySetBreakpoint(breakpt: stackdriver.Breakpoint) { - // Set a breakpoint at which the debugger should write to a log - const breakpoint = await api.setBreakpoint(debuggeeId!, breakpt); - - // Check that the breakpoint was set, and then wait for the log to be - // written to - assert.ok(breakpoint, 'should have set a breakpoint'); - assert.ok(breakpoint.id, 'breakpoint should have an id'); - assert.ok(breakpoint.location, 'breakpoint should have a location'); - assert.strictEqual(breakpoint.location!.path, FILENAME); - - console.log('-- waiting for the breakpoint/logpoint to hit'); - await delay(10 * 1000); - - return breakpoint; - } - - async function verifySetLogpoint() { - console.log('-- setting a logpoint'); - await verifySetBreakpoint({ - id: 'breakpoint-1', - location: {path: FILENAME, line: 5}, - condition: 'n === 10', - action: 'LOG', - expressions: ['o'], - logMessageFormat: LOG_MESSAGE_FORMAT, - stackFrames: [], - evaluatedExpressions: [], - variableTable: [], - }); - - // Check the contents of the log, but keep the original breakpoint. - children.forEach((child, index) => { - assert( - child.transcript.indexOf(`${UUID}: o is: {"a":[1,"hi",true]}`) !== -1, - 'transcript in child ' + - index + - ' should contain value of o: ' + - child.transcript - ); - }); - } - - async function verifySetDuplicateBreakpoint() { - // Set another breakpoint at the same location - console.log('-- setting a breakpoint'); - const breakpoint = await verifySetBreakpoint({ - id: 'breakpoint-2', - location: {path: FILENAME, line: 5}, - expressions: ['process'], // Process for large variable - condition: 'n === 10', - logMessageFormat: LOG_MESSAGE_FORMAT, - stackFrames: [], - evaluatedExpressions: [], - variableTable: [], - }); - - console.log('-- now checking if the breakpoint was hit'); - const foundBreakpoint = await api.getBreakpoint(debuggeeId!, breakpoint.id); - - // Check that the breakpoint was hit and contains the correct - // information, which ends the test - console.log('-- results of get breakpoint\n', foundBreakpoint); - assert.ok(foundBreakpoint, 'should have a breakpoint in the response'); - assert.ok(foundBreakpoint.isFinalState, 'breakpoint should have been hit'); - assert.ok(Array.isArray(foundBreakpoint.stackFrames), 'should have stack '); - const top = foundBreakpoint.stackFrames[0]; - assert.ok(top, 'should have a top entry'); - assert.ok(top.function, 'frame should have a function property'); - assert.strictEqual(top.function, 'fib'); - - const arg = top.locals.find(t => t.name === 'n'); - assert.ok(arg, 'should find the n argument'); - assert.strictEqual(arg!.value, '10'); - console.log('-- checking log point was hit again'); - children.forEach(child => { - const count = (child.transcript.match(REGEX) || []).length; - assert.ok(count > 4); - }); - - await api.deleteBreakpoint(debuggeeId!, foundBreakpoint.id); - } - - async function verifyHitLogpoint() { - // wait for 60 seconds - console.log('-- waiting for 60 seconds'); - await delay(60 * 1000); - - // Make sure the log point is continuing to be hit. - console.log('-- checking log point was hit again'); - children.forEach(child => { - const count = (child.transcript.match(REGEX) || []).length; - assert.ok(count > 20, `expected count ${count} to be > 20`); - }); - console.log('-- test passed'); - } - - it('should set breakpoints correctly', async function () { - this.timeout(90 * 1000); - await verifyDebuggeeFound(); - await verifyDeleteBreakpoints(); - await verifySetLogpoint(); - await verifySetDuplicateBreakpoint(); - await verifyHitLogpoint(); - }); - - it('should throttle logs correctly', async function () { - this.timeout(15 * 1000); - - await verifyDebuggeeFound(); - - const breakpoints = await api.listBreakpoints(debuggeeId!, {}); - console.log('-- List of breakpoints\n', breakpoints); - - await verifyDeleteBreakpoints(); - - const foundBreakpoints = await api.listBreakpoints(debuggeeId!, {}); - assert.strictEqual(foundBreakpoints.length, 0); - console.log('-- deleted'); - - // Set a breakpoint at which the debugger should write to a log - console.log('-- setting a logpoint'); - const breakpoint = await verifySetBreakpoint({ - id: 'breakpoint-3', - location: {path: FILENAME, line: 5}, - condition: 'n === 10', - action: 'LOG', - expressions: ['o'], - logMessageFormat: LOG_MESSAGE_FORMAT, - stackFrames: [], - evaluatedExpressions: [], - variableTable: [], - }); - - // If no throttling occurs, we expect ~20 logs since we are logging - // 2x per second over a 10 second period. - children.forEach(child => { - const logCount = (child.transcript.match(REGEX) || []).length; - // A log count of greater than 10 indicates that we did not - // successfully pause when the rate of `maxLogsPerSecond` was - // reached. - assert(logCount <= 10, 'log count is greater than 10: ' + logCount); - // A log count of less than 3 indicates that we did not successfully - // resume logging after `logDelaySeconds` have passed. - assert(logCount > 2, 'log count is not greater than 2: ' + logCount); - }); - - await api.deleteBreakpoint(debuggeeId!, breakpoint.id); - console.log('-- test passed'); - }); -}); +// import * as assert from 'assert'; +// import {afterEach, before, beforeEach, describe, it} from 'mocha'; +// import * as cp from 'child_process'; +// import * as util from 'util'; +// import * as uuid from 'uuid'; + +// import * as stackdriver from '../src/types/stackdriver'; +// import {Debugger} from '../test/debugger'; + +// const CLUSTER_WORKERS = 3; + +// const FILENAME = 'build/test/fixtures/fib.js'; + +// const UUID = uuid.v4(); +// const LOG_MESSAGE_FORMAT = UUID + ': o is: $0'; + +// // fib.js uses a custom log-point function that lower cases everything. +// const REGEX = new RegExp( +// `logpoint: ${UUID}: o is: {"a":\\[1,"hi",true\\]}`, +// 'g' +// ); + +// const delay = (delayTimeMS: number): Promise => { +// return new Promise(r => setTimeout(r, delayTimeMS)); +// }; + +// interface Child { +// transcript: string; +// process?: cp.ChildProcess; +// } + +// TODO: Write E2E tests that work with the Firebase backend. +// // This test could take up to 100 seconds. +// describe('@google-cloud/debug end-to-end behavior', () => { +// let api: Debugger; + +// let debuggeeId: string | null; +// let projectId: string | null; +// let children: Child[] = []; + +// before(() => { +// const packageInfo = {name: 'Some name', version: 'Some version'}; +// api = new Debugger(new Debug({}, packageInfo)); +// }); + +// beforeEach(function () { +// this.timeout(10 * 1000); +// return new Promise((resolve, reject) => { +// let numChildrenReady = 0; + +// // Process a status message sent from a child process. +// const handler = (c: { +// error: Error | null; +// debuggeeId: string; +// projectId: string; +// }) => { +// console.log('handler received:', c); +// if (c.error) { +// reject(new Error('A child reported the following error: ' + c.error)); +// return; +// } +// if (!debuggeeId) { +// // Cache the needed info from the first worker. +// debuggeeId = c.debuggeeId; +// projectId = c.projectId; +// } else { +// // Make sure all other workers are consistent. +// if (debuggeeId !== c.debuggeeId || projectId !== c.projectId) { +// reject( +// new Error( +// 'Child debuggee ID and/or project ID' + +// 'is not consistent with previous child' +// ) +// ); +// return; +// } +// } +// numChildrenReady++; +// if (numChildrenReady === CLUSTER_WORKERS) { +// console.log('All children are ready'); +// resolve(); +// } +// }; + +// // Handle stdout/stderr output from a child process. More specifically, +// // write the child process's output to a transcript. +// // Each child has its own transcript. +// const stdoutHandler = (index: number) => { +// return (chunk: string) => { +// children[index].transcript += chunk; +// }; +// }; + +// for (let i = 0; i < CLUSTER_WORKERS; i++) { +// // Fork child processes that send messages to this process with IPC. +// // We pass UUID to the children so that they can all get the same +// // debuggee id. +// const child: Child = {transcript: ''}; +// child.process = cp.fork(FILENAME, /* args */ [UUID], { +// execArgv: [], +// env: process.env, +// silent: true, +// }); +// child.process.on('message', handler); + +// children.push(child); +// child.process.stdout!.on('data', stdoutHandler(i)); +// child.process.stderr!.on('data', stdoutHandler(i)); +// } +// }); +// }); + +// afterEach(function () { +// this.timeout(5 * 1000); +// // Create a promise for each child that resolves when that child exits. +// const childExitPromises = children.map((child, index) => { +// console.log(`child ${index} transcript: ===#`, child.transcript, '#==='); +// assert(child.process); +// const childProcess = child.process as cp.ChildProcess; +// childProcess.kill(); +// return new Promise((resolve, reject) => { +// const timeout = setTimeout(() => { +// reject(new Error('A child process failed to exit.')); +// }, 3000); +// childProcess.on('exit', () => { +// clearTimeout(timeout); +// resolve(); +// }); +// }); +// }); +// // Wait until all children exit, then reset test state. +// return Promise.all(childExitPromises).then(() => { +// debuggeeId = null; +// projectId = null; +// children = []; +// }); +// }); + +// async function verifyDebuggeeFound() { +// const allDebuggees = await api.listDebuggees(projectId!, true); +// const debuggees = allDebuggees.filter( +// debuggee => debuggee.isInactive !== true +// ); + +// // Check that the debuggee created in this test is among the list of +// // debuggees, then list its breakpoints +// console.log( +// '-- List of debuggees\n', +// util.inspect(debuggees, {depth: null}) +// ); +// assert.ok(debuggees, 'should get a valid ListDebuggees response'); + +// const result = debuggees.find(d => d.id === debuggeeId); +// assert.ok( +// result, +// `should find the debuggee we just registered, expected debuggeeId: ${debuggeeId}, found: ${result}` +// ); +// } + +// async function verifyDeleteBreakpoints() { +// // Delete every breakpoint +// const breakpoints = await api.listBreakpoints(debuggeeId!, {}); +// console.log('-- List of breakpoints\n', breakpoints); + +// const promises = breakpoints.map(breakpoint => { +// return api.deleteBreakpoint(debuggeeId!, breakpoint.id); +// }); +// await Promise.all(promises); + +// const breakpointsAfterDelete = await api.listBreakpoints(debuggeeId!, {}); +// assert.strictEqual(breakpointsAfterDelete.length, 0); +// console.log('-- deleted'); +// } + +// async function verifySetBreakpoint(breakpt: stackdriver.Breakpoint) { +// // Set a breakpoint at which the debugger should write to a log +// const breakpoint = await api.setBreakpoint(debuggeeId!, breakpt); + +// // Check that the breakpoint was set, and then wait for the log to be +// // written to +// assert.ok(breakpoint, 'should have set a breakpoint'); +// assert.ok(breakpoint.id, 'breakpoint should have an id'); +// assert.ok(breakpoint.location, 'breakpoint should have a location'); +// assert.strictEqual(breakpoint.location!.path, FILENAME); + +// console.log('-- waiting for the breakpoint/logpoint to hit'); +// await delay(10 * 1000); + +// return breakpoint; +// } + +// async function verifySetLogpoint() { +// console.log('-- setting a logpoint'); +// await verifySetBreakpoint({ +// id: 'breakpoint-1', +// location: {path: FILENAME, line: 5}, +// condition: 'n === 10', +// action: 'LOG', +// expressions: ['o'], +// logMessageFormat: LOG_MESSAGE_FORMAT, +// stackFrames: [], +// evaluatedExpressions: [], +// variableTable: [], +// }); + +// // Check the contents of the log, but keep the original breakpoint. +// children.forEach((child, index) => { +// assert( +// child.transcript.indexOf(`${UUID}: o is: {"a":[1,"hi",true]}`) !== -1, +// 'transcript in child ' + +// index + +// ' should contain value of o: ' + +// child.transcript +// ); +// }); +// } + +// async function verifySetDuplicateBreakpoint() { +// // Set another breakpoint at the same location +// console.log('-- setting a breakpoint'); +// const breakpoint = await verifySetBreakpoint({ +// id: 'breakpoint-2', +// location: {path: FILENAME, line: 5}, +// expressions: ['process'], // Process for large variable +// condition: 'n === 10', +// logMessageFormat: LOG_MESSAGE_FORMAT, +// stackFrames: [], +// evaluatedExpressions: [], +// variableTable: [], +// }); + +// console.log('-- now checking if the breakpoint was hit'); +// const foundBreakpoint = await api.getBreakpoint(debuggeeId!, breakpoint.id); + +// // Check that the breakpoint was hit and contains the correct +// // information, which ends the test +// console.log('-- results of get breakpoint\n', foundBreakpoint); +// assert.ok(foundBreakpoint, 'should have a breakpoint in the response'); +// assert.ok(foundBreakpoint.isFinalState, 'breakpoint should have been hit'); +// assert.ok(Array.isArray(foundBreakpoint.stackFrames), 'should have stack '); +// const top = foundBreakpoint.stackFrames[0]; +// assert.ok(top, 'should have a top entry'); +// assert.ok(top.function, 'frame should have a function property'); +// assert.strictEqual(top.function, 'fib'); + +// const arg = top.locals.find(t => t.name === 'n'); +// assert.ok(arg, 'should find the n argument'); +// assert.strictEqual(arg!.value, '10'); +// console.log('-- checking log point was hit again'); +// children.forEach(child => { +// const count = (child.transcript.match(REGEX) || []).length; +// assert.ok(count > 4); +// }); + +// await api.deleteBreakpoint(debuggeeId!, foundBreakpoint.id); +// } + +// async function verifyHitLogpoint() { +// // wait for 60 seconds +// console.log('-- waiting for 60 seconds'); +// await delay(60 * 1000); + +// // Make sure the log point is continuing to be hit. +// console.log('-- checking log point was hit again'); +// children.forEach(child => { +// const count = (child.transcript.match(REGEX) || []).length; +// assert.ok(count > 20, `expected count ${count} to be > 20`); +// }); +// console.log('-- test passed'); +// } + +// it('should set breakpoints correctly', async function () { +// this.timeout(90 * 1000); +// await verifyDebuggeeFound(); +// await verifyDeleteBreakpoints(); +// await verifySetLogpoint(); +// await verifySetDuplicateBreakpoint(); +// await verifyHitLogpoint(); +// }); + +// it('should throttle logs correctly', async function () { +// this.timeout(15 * 1000); + +// await verifyDebuggeeFound(); + +// const breakpoints = await api.listBreakpoints(debuggeeId!, {}); +// console.log('-- List of breakpoints\n', breakpoints); + +// await verifyDeleteBreakpoints(); + +// const foundBreakpoints = await api.listBreakpoints(debuggeeId!, {}); +// assert.strictEqual(foundBreakpoints.length, 0); +// console.log('-- deleted'); + +// // Set a breakpoint at which the debugger should write to a log +// console.log('-- setting a logpoint'); +// const breakpoint = await verifySetBreakpoint({ +// id: 'breakpoint-3', +// location: {path: FILENAME, line: 5}, +// condition: 'n === 10', +// action: 'LOG', +// expressions: ['o'], +// logMessageFormat: LOG_MESSAGE_FORMAT, +// stackFrames: [], +// evaluatedExpressions: [], +// variableTable: [], +// }); + +// // If no throttling occurs, we expect ~20 logs since we are logging +// // 2x per second over a 10 second period. +// children.forEach(child => { +// const logCount = (child.transcript.match(REGEX) || []).length; +// // A log count of greater than 10 indicates that we did not +// // successfully pause when the rate of `maxLogsPerSecond` was +// // reached. +// assert(logCount <= 10, 'log count is greater than 10: ' + logCount); +// // A log count of less than 3 indicates that we did not successfully +// // resume logging after `logDelaySeconds` have passed. +// assert(logCount > 2, 'log count is not greater than 2: ' + logCount); +// }); + +// await api.deleteBreakpoint(debuggeeId!, breakpoint.id); +// console.log('-- test passed'); +// }); +// }); diff --git a/test/debugger.ts b/test/debugger.ts index e794a502..3945b38f 100644 --- a/test/debugger.ts +++ b/test/debugger.ts @@ -16,49 +16,16 @@ * @module debug/debugger */ -import {ServiceObject} from '@google-cloud/common'; -import {Debug} from '../src/client/stackdriver/debug'; -import {Debuggee} from '../src/debuggee'; +// import {Debuggee} from '../src/debuggee'; import * as stackdriver from '../src/types/stackdriver'; -// TODO: Verify these types are correct. -const qs: { - parse: ( - qs: {}, - sep?: string, - eq?: string, - options?: {maxKeys?: number} - ) => {}; - stringify: ( - obj: object | string | boolean | number, - sep?: string, - eq?: string, - name?: string - ) => string; - // eslint-disable-next-line @typescript-eslint/no-var-requires -} = require('querystring'); - -/** @const {string} Cloud Debug API endpoint */ -const API = 'https://clouddebugger.googleapis.com/v2/debugger'; - -export class Debugger extends ServiceObject { - private nextWaitToken: string | null; - private clientVersion: string; +// TODO: Write a Debugger interface that works with the Firebase backend. +export class Debugger { /** * @constructor */ - constructor(debug: Debug) { - super({parent: debug, baseUrl: '/debugger'}); - - /** @private {string} */ - this.nextWaitToken = null; - - this.clientVersion = - debug.packageInfo.name + - '/client-for-testing/v' + - debug.packageInfo.version; - } + constructor() {} /** * Gets a list of debuggees in a given project to which the user can set @@ -67,42 +34,8 @@ export class Debugger extends ServiceObject { * should be listed. * @param {boolean=} includeInactive - Whether or not to include inactive * debuggees in the list (default false). - * @param {!function(?Error,Debuggee[]=)} callback - A function that will be - * called with a list of Debuggee objects as a parameter, or an Error - * object if an error occurred in obtaining it. */ - async listDebuggees(projectId: string, includeInactive: boolean) { - return new Promise((resolve, reject) => { - const query = { - clientVersion: this.clientVersion, - includeInactive, - project: projectId, - }; - - const uri = API + '/debuggees?' + qs.stringify(query); - this.request({uri, json: true}, (err, body, response) => { - if (err) { - reject(err); - } else if (!response) { - reject(new Error('unknown error - request response missing')); - } else if (response.statusCode !== 200) { - reject( - new Error( - 'unable to list debuggees, status code ' + response.statusCode - ) - ); - } else if (!body) { - reject(new Error('invalid response body from server')); - } else { - if (body.debuggees) { - resolve(body.debuggees); - } else { - resolve([]); - } - } - }); - }); - } + async listDebuggees(projectId: string, includeInactive: boolean) {} /** * Gets a list of breakpoints in a given debuggee. @@ -116,9 +49,6 @@ export class Debugger extends ServiceObject { * inactive breakpoints in the list (default false). * @param {Action=} options.action - Either 'CAPTURE' or 'LOG'. If specified, * only breakpoints with a matching action will be part of the list. - * @param {!function(?Error,Breakpoint[]=)} callback - A function that will be - * called with a list of Breakpoint objects as a parameter, or an Error - * object if an error occurred in obtaining it. */ listBreakpoints( debuggeeId: string, @@ -127,57 +57,7 @@ export class Debugger extends ServiceObject { includeInactive?: boolean; action?: stackdriver.Action; } - ) { - return new Promise((resolve, reject) => { - // TODO: Remove this cast as `any` - const query: { - clientVersion: string; - includeAllUsers: boolean; - includeInactive: boolean; - action?: {value: stackdriver.Action}; - waitToken?: string; - } = { - clientVersion: this.clientVersion, - includeAllUsers: !!options.includeAllUsers, - includeInactive: !!options.includeInactive, - }; - // TODO: Determine how to remove this cast. - if (options.action) { - query.action = {value: options.action}; - } - if (this.nextWaitToken) { - query.waitToken = this.nextWaitToken; - } - - const uri = - API + - '/debuggees/' + - encodeURIComponent(debuggeeId) + - '/breakpoints?' + - qs.stringify(query); - this.request({uri, json: true}, (err, body, response) => { - if (err) { - reject(err); - } else if (!response) { - reject(new Error('unknown error - request response missing')); - } else if (response.statusCode !== 200) { - reject( - new Error( - 'unable to list breakpoints, status code ' + response.statusCode - ) - ); - } else if (!body) { - reject(new Error('invalid response body from server')); - } else { - if (body.breakpoints) { - resolve(body.breakpoints); - } else { - resolve([]); - } - } - }); - }); - } + ) {} /** * Gets information about a given breakpoint. @@ -185,42 +65,8 @@ export class Debugger extends ServiceObject { * is set. * @param {string} breakpointId - The ID of the breakpoint to get information * about. - * @param {!function(?Error,Breakpoint=)} callback - A function that will be - * called with information about the given breakpoint, or an Error object - * if an error occurred in obtaining its information. */ - getBreakpoint(debuggeeId: string, breakpointId: string) { - return new Promise((resolve, reject) => { - const query = {clientVersion: this.clientVersion}; - - const uri = - API + - '/debuggees/' + - encodeURIComponent(debuggeeId) + - '/breakpoints/' + - encodeURIComponent(breakpointId) + - '?' + - qs.stringify(query); - this.request({uri, json: true}, (err, body, response) => { - if (err) { - reject(err); - } else if (!response) { - reject(new Error('unknown error - request response missing')); - } else if (response.statusCode !== 200) { - reject( - new Error( - 'unable to get breakpoint info, status code ' + - response.statusCode - ) - ); - } else if (!body || !body.breakpoint) { - reject(new Error('invalid response body from server')); - } else { - resolve(body.breakpoint); - } - }); - }); - } + getBreakpoint(debuggeeId: string, breakpointId: string) {} /** * Sets a new breakpoint. @@ -228,90 +74,14 @@ export class Debugger extends ServiceObject { * should be set. * @param {Breakpoint} breakpoint - An object representing information about * the breakpoint to set. - * @param {!function(?Error,Breakpoint=)} callback - A function that will be - * called with information about the breakpoint that was just set, or an - * Error object if an error occurred in obtaining its information. Note - * that the Breakpoint object here will differ from the input object in - * that its id field will be set. */ - setBreakpoint(debuggeeId: string, breakpoint: stackdriver.Breakpoint) { - return new Promise((resolve, reject) => { - const query = {clientVersion: this.clientVersion}; - const options = { - uri: - API + - '/debuggees/' + - encodeURIComponent(debuggeeId) + - '/breakpoints/set?' + - qs.stringify(query), - method: 'POST', - json: true, - body: breakpoint, - }; - - this.request(options, (err, body, response) => { - if (err) { - reject(err); - } else if (!response) { - reject(new Error('unknown error - request response missing')); - } else if (response.statusCode !== 200) { - reject( - new Error( - 'unable to set breakpoint, status code ' + response.statusCode - ) - ); - } else if (!body || !body.breakpoint) { - reject(new Error('invalid response body from server')); - } else { - resolve(body.breakpoint); - } - }); - }); - } + setBreakpoint(debuggeeId: string, breakpoint: stackdriver.Breakpoint) {} /** * Deletes a breakpoint. * @param {Debuggee} debuggeeId - The ID of the debuggee to which the breakpoint * belongs. * @param {Breakpoint} breakpointId - The ID of the breakpoint to delete. - * @param {!function(?Error)} callback - A function that will be - * called with a Error object as a parameter if an error occurred in - * deleting a breakpoint. If no error occurred, the first argument will be - * set to null. */ - deleteBreakpoint(debuggeeId: string, breakpointId: string) { - return new Promise((resolve, reject) => { - const query = {clientVersion: this.clientVersion}; - const options = { - uri: - API + - '/debuggees/' + - encodeURIComponent(debuggeeId) + - '/breakpoints/' + - encodeURIComponent(breakpointId) + - '?' + - qs.stringify(query), - method: 'DELETE', - json: true, - }; - - this.request(options, (err, body, response) => { - if (err) { - reject(err); - } else if (!response) { - reject(new Error('unknown error - request response missing')); - } else if (response.statusCode !== 200) { - reject( - new Error( - 'unable to delete breakpoint, status code ' + response.statusCode - ) - ); - } else if (Object.keys(body).length > 0) { - reject(new Error('response body is non-empty')); - } else { - resolve(); - } - }); - }); - } + deleteBreakpoint(debuggeeId: string, breakpointId: string) {} } diff --git a/test/test-controller.ts b/test/test-controller.ts index 7637f1aa..d7ed54ab 100644 --- a/test/test-controller.ts +++ b/test/test-controller.ts @@ -12,377 +12,378 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as assert from 'assert'; -import {before, describe, it} from 'mocha'; -import * as nock from 'nock'; +// import * as assert from 'assert'; +// import {before, describe, it} from 'mocha'; +// import * as nock from 'nock'; -import {Debug} from '../src/client/stackdriver/debug'; -import {Debuggee} from '../src/debuggee'; -import {defaultConfig as DEFAULT_CONFIG} from '../src/agent/config'; -import * as stackdriver from '../src/types/stackdriver'; -import * as t from 'teeny-request'; // types only -import {teenyRequest} from 'teeny-request'; -import {MockLogger} from './mock-logger'; +// import {Debuggee} from '../src/debuggee'; +// import {defaultConfig as DEFAULT_CONFIG} from '../src/agent/config'; +// import * as stackdriver from '../src/types/stackdriver'; +// import * as t from 'teeny-request'; // types only +// import {teenyRequest} from 'teeny-request'; +// import {MockLogger} from './mock-logger'; + +// TODO: Write controller tests that use the Firebase controller. // the tests in this file rely on the GCLOUD_PROJECT environment variable // not being set -delete process.env.GCLOUD_PROJECT; +// delete process.env.GCLOUD_PROJECT; -import {OnePlatformController} from '../src/agent/oneplatform-controller'; -// TODO: Fix fakeDebug to actually implement Debug. -const fakeDebug = { - apiEndpoint: 'clouddebugger.googleapis.com', - request: (options: t.Options, cb: t.RequestCallback) => { - teenyRequest(options, (err, r) => { - cb(err, r ? r.body : undefined, r); - }); - }, -} as {} as Debug; +// import {FirebaseController} from '../src/agent/firebase-controller'; +// TODO: Probably use the fake firebase backend. +// const fakeDebug = { +// apiEndpoint: 'clouddebugger.googleapis.com', +// request: (options: t.Options, cb: t.RequestCallback) => { +// teenyRequest(options, (err, r) => { +// cb(err, r ? r.body : undefined, r); +// }); +// }, +// } as {} as Debug; -const agentVersion = 'SomeName/client/SomeVersion'; -const url = 'https://clouddebugger.googleapis.com'; -const api = '/v2/controller'; +// const agentVersion = 'SomeName/client/SomeVersion'; +// const url = 'https://clouddebugger.googleapis.com'; +// const api = '/v2/controller'; -nock.disableNetConnect(); +// nock.disableNetConnect(); -describe('Controller API', () => { - const logger = new MockLogger(); +// describe('Controller API', () => { +// const logger = new MockLogger(); - describe('register', () => { - it('should get a debuggeeId', done => { - const scope = nock(url) - .post(api + '/debuggees/register') - .reply(200, { - debuggee: {id: 'fake-debuggee'}, - activePeriodSec: 600, - }); - const debuggee = new Debuggee({ - project: 'fake-project', - uniquifier: 'fake-id', - description: 'unit test', - agentVersion, - }); - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - // TODO: Determine if this type signature is correct. - controller.register(debuggee, (err, result) => { - assert(!err, 'not expecting an error'); - assert.ok(result); - assert.strictEqual(result!.debuggee.id, 'fake-debuggee'); - scope.done(); - done(); - }); - }); +// describe('register', () => { +// it('should get a debuggeeId', done => { +// const scope = nock(url) +// .post(api + '/debuggees/register') +// .reply(200, { +// debuggee: {id: 'fake-debuggee'}, +// activePeriodSec: 600, +// }); +// const debuggee = new Debuggee({ +// project: 'fake-project', +// uniquifier: 'fake-id', +// description: 'unit test', +// agentVersion, +// }); +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// // TODO: Determine if this type signature is correct. +// controller.register(debuggee, (err, result) => { +// assert(!err, 'not expecting an error'); +// assert.ok(result); +// assert.strictEqual(result!.debuggee.id, 'fake-debuggee'); +// scope.done(); +// done(); +// }); +// }); - it('should get an agentId', done => { - const scope = nock(url) - .post(api + '/debuggees/register') - .reply(200, { - debuggee: {id: 'fake-debuggee'}, - agentId: 'fake-agent-id', - activePeriodSec: 600, - }); - const debuggee = new Debuggee({ - project: 'fake-project', - uniquifier: 'fake-id', - description: 'unit test', - agentVersion, - }); - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - // TODO: Determine if this type signature is correct. - controller.register(debuggee, (err, result) => { - assert(!err, 'not expecting an error'); - assert.ok(result); - assert.strictEqual(result!.agentId, 'fake-agent-id'); - scope.done(); - done(); - }); - }); +// it('should get an agentId', done => { +// const scope = nock(url) +// .post(api + '/debuggees/register') +// .reply(200, { +// debuggee: {id: 'fake-debuggee'}, +// agentId: 'fake-agent-id', +// activePeriodSec: 600, +// }); +// const debuggee = new Debuggee({ +// project: 'fake-project', +// uniquifier: 'fake-id', +// description: 'unit test', +// agentVersion, +// }); +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// // TODO: Determine if this type signature is correct. +// controller.register(debuggee, (err, result) => { +// assert(!err, 'not expecting an error'); +// assert.ok(result); +// assert.strictEqual(result!.agentId, 'fake-agent-id'); +// scope.done(); +// done(); +// }); +// }); - it('should not return an error when the debuggee isDisabled', done => { - const scope = nock(url) - .post(api + '/debuggees/register') - .reply(200, { - debuggee: {id: 'fake-debuggee', isDisabled: true}, - activePeriodSec: 600, - }); - const debuggee = new Debuggee({ - project: 'fake-project', - uniquifier: 'fake-id', - description: 'unit test', - agentVersion, - }); - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - controller.register(debuggee, (err, result) => { - // TODO: Fix this incorrect method signature. - (assert as {ifError: Function}).ifError(err, 'not expecting an error'); - assert.ok(result); - assert.strictEqual(result!.debuggee.id, 'fake-debuggee'); - assert.ok(result!.debuggee.isDisabled); - scope.done(); - done(); - }); - }); - }); +// it('should not return an error when the debuggee isDisabled', done => { +// const scope = nock(url) +// .post(api + '/debuggees/register') +// .reply(200, { +// debuggee: {id: 'fake-debuggee', isDisabled: true}, +// activePeriodSec: 600, +// }); +// const debuggee = new Debuggee({ +// project: 'fake-project', +// uniquifier: 'fake-id', +// description: 'unit test', +// agentVersion, +// }); +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// controller.register(debuggee, (err, result) => { +// // TODO: Fix this incorrect method signature. +// (assert as {ifError: Function}).ifError(err, 'not expecting an error'); +// assert.ok(result); +// assert.strictEqual(result!.debuggee.id, 'fake-debuggee'); +// assert.ok(result!.debuggee.isDisabled); +// scope.done(); +// done(); +// }); +// }); +// }); - describe('listBreakpoints', () => { - // register before each test - before(done => { - nock(url) - .post(api + '/debuggees/register') - .reply(200, { - debuggee: {id: 'fake-debuggee'}, - activePeriodSec: 600, - }); - const debuggee = new Debuggee({ - project: 'fake-project', - uniquifier: 'fake-id', - description: 'unit test', - agentVersion, - }); - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - controller.register(debuggee, (err /*, result*/) => { - assert.ifError(err); - done(); - }); - }); +// describe('listBreakpoints', () => { +// // register before each test +// before(done => { +// nock(url) +// .post(api + '/debuggees/register') +// .reply(200, { +// debuggee: {id: 'fake-debuggee'}, +// activePeriodSec: 600, +// }); +// const debuggee = new Debuggee({ +// project: 'fake-project', +// uniquifier: 'fake-id', +// description: 'unit test', +// agentVersion, +// }); +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// controller.register(debuggee, (err /*, result*/) => { +// assert.ifError(err); +// done(); +// }); +// }); - it('should deal with a missing breakpoints response', done => { - const scope = nock(url) - .get(api + '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true') - .reply(200, {kind: 'whatever'}); +// it('should deal with a missing breakpoints response', done => { +// const scope = nock(url) +// .get(api + '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true') +// .reply(200, {kind: 'whatever'}); - const debuggee = {id: 'fake-debuggee'}; - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - // TODO: Fix debuggee to actually implement Debuggee - // TODO: Determine if the response parameter should be used. - controller.listBreakpoints( - debuggee as Debuggee, - (err, response, result?: stackdriver.ListBreakpointsResponse) => { - assert(!err, 'not expecting an error'); - // TODO: Handle the case where result is undefined - assert( - !(result as {breakpoints: {}}).breakpoints, - 'should not have a breakpoints property' - ); - scope.done(); - done(); - } - ); - }); +// const debuggee = {id: 'fake-debuggee'}; +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// // TODO: Fix debuggee to actually implement Debuggee +// // TODO: Determine if the response parameter should be used. +// controller.listBreakpoints( +// debuggee as Debuggee, +// (err, response, result?: stackdriver.ListBreakpointsResponse) => { +// assert(!err, 'not expecting an error'); +// // TODO: Handle the case where result is undefined +// assert( +// !(result as {breakpoints: {}}).breakpoints, +// 'should not have a breakpoints property' +// ); +// scope.done(); +// done(); +// } +// ); +// }); - describe('invalid responses', () => { - const tests: string | Array<{}> = ['', 'JSON, this is not', []]; - tests.forEach((invalidResponse, index) => { - it('should pass test ' + index, done => { - const scope = nock(url) - .get( - api + '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true' - ) - .reply(200, invalidResponse); - const debuggee = {id: 'fake-debuggee'}; - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - // TODO: Fix debuggee to actually implement Debuggee - // TODO: Determine if the response parameter should be used. - controller.listBreakpoints( - debuggee as Debuggee, - (err, response, result?: stackdriver.ListBreakpointsResponse) => { - assert(!err, 'not expecting an error'); - // TODO: Handle the case where result is undefined - assert( - !(result as {breakpoints: {}}).breakpoints, - 'should not have breakpoints property' - ); - scope.done(); - done(); - } - ); - }); - }); - }); +// describe('invalid responses', () => { +// const tests: string | Array<{}> = ['', 'JSON, this is not', []]; +// tests.forEach((invalidResponse, index) => { +// it('should pass test ' + index, done => { +// const scope = nock(url) +// .get( +// api + '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true' +// ) +// .reply(200, invalidResponse); +// const debuggee = {id: 'fake-debuggee'}; +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// // TODO: Fix debuggee to actually implement Debuggee +// // TODO: Determine if the response parameter should be used. +// controller.listBreakpoints( +// debuggee as Debuggee, +// (err, response, result?: stackdriver.ListBreakpointsResponse) => { +// assert(!err, 'not expecting an error'); +// // TODO: Handle the case where result is undefined +// assert( +// !(result as {breakpoints: {}}).breakpoints, +// 'should not have breakpoints property' +// ); +// scope.done(); +// done(); +// } +// ); +// }); +// }); +// }); - it('should throw error on http errors', done => { - const scope = nock(url) - .get(api + '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true') - .reply(403); - // TODO: Fix debuggee to actually implement Debuggee - const debuggee: Debuggee = {id: 'fake-debuggee'} as Debuggee; - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - // TODO: Determine if the response parameter should be used. - controller.listBreakpoints(debuggee, (err, response, result) => { - assert(err instanceof Error, 'expecting an error'); - assert(!result, 'should not have a result'); - scope.done(); - done(); - }); - }); +// it('should throw error on http errors', done => { +// const scope = nock(url) +// .get(api + '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true') +// .reply(403); +// // TODO: Fix debuggee to actually implement Debuggee +// const debuggee: Debuggee = {id: 'fake-debuggee'} as Debuggee; +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// // TODO: Determine if the response parameter should be used. +// controller.listBreakpoints(debuggee, (err, response, result) => { +// assert(err instanceof Error, 'expecting an error'); +// assert(!result, 'should not have a result'); +// scope.done(); +// done(); +// }); +// }); - it('should work with waitTokens', done => { - const scope = nock(url) - .get(api + '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true') - .reply(200, {waitExpired: true}); - // TODO: Fix debuggee to actually implement Debuggee - const debuggee: Debuggee = {id: 'fake-debuggee'} as Debuggee; - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - // TODO: Determine if the result parameter should be used. - controller.listBreakpoints(debuggee, (err, response) => { - // TODO: Fix this incorrect method signature. - (assert as {ifError: Function}).ifError(err, 'not expecting an error'); - // TODO: Fix this error that states `body` is not a property - // of `ServerResponse`. - assert( - (response as {} as {body: {waitExpired: {}}}).body.waitExpired, - 'should have expired set' - ); - scope.done(); - done(); - }); - }); +// it('should work with waitTokens', done => { +// const scope = nock(url) +// .get(api + '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true') +// .reply(200, {waitExpired: true}); +// // TODO: Fix debuggee to actually implement Debuggee +// const debuggee: Debuggee = {id: 'fake-debuggee'} as Debuggee; +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// // TODO: Determine if the result parameter should be used. +// controller.listBreakpoints(debuggee, (err, response) => { +// // TODO: Fix this incorrect method signature. +// (assert as {ifError: Function}).ifError(err, 'not expecting an error'); +// // TODO: Fix this error that states `body` is not a property +// // of `ServerResponse`. +// assert( +// (response as {} as {body: {waitExpired: {}}}).body.waitExpired, +// 'should have expired set' +// ); +// scope.done(); +// done(); +// }); +// }); - it('should work with agentId provided from registration', done => { - const scope = nock(url) - .post(api + '/debuggees/register') - .reply(200, { - debuggee: {id: 'fake-debuggee'}, - agentId: 'fake-agent-id', - activePeriodSec: 600, - }) - .get( - api + - '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true&agentId=fake-agent-id' - ) - .reply(200, {waitExpired: true}); - const debuggee = new Debuggee({ - project: 'fake-project', - uniquifier: 'fake-id', - description: 'unit test', - agentVersion, - }); - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - controller.register(debuggee, (err1 /*, response1*/) => { - assert.ifError(err1); - const debuggeeWithId: Debuggee = {id: 'fake-debuggee'} as Debuggee; - // TODO: Determine if the result parameter should be used. - controller.listBreakpoints(debuggeeWithId, (err2 /*, response2*/) => { - assert.ifError(err2); - scope.done(); - done(); - }); - }); - }); +// it('should work with agentId provided from registration', done => { +// const scope = nock(url) +// .post(api + '/debuggees/register') +// .reply(200, { +// debuggee: {id: 'fake-debuggee'}, +// agentId: 'fake-agent-id', +// activePeriodSec: 600, +// }) +// .get( +// api + +// '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true&agentId=fake-agent-id' +// ) +// .reply(200, {waitExpired: true}); +// const debuggee = new Debuggee({ +// project: 'fake-project', +// uniquifier: 'fake-id', +// description: 'unit test', +// agentVersion, +// }); +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// controller.register(debuggee, (err1 /*, response1*/) => { +// assert.ifError(err1); +// const debuggeeWithId: Debuggee = {id: 'fake-debuggee'} as Debuggee; +// // TODO: Determine if the result parameter should be used. +// controller.listBreakpoints(debuggeeWithId, (err2 /*, response2*/) => { +// assert.ifError(err2); +// scope.done(); +// done(); +// }); +// }); +// }); - // TODO: Fix this so that each element of the array is actually an - // array of Breakpoints. - const testsBreakpoints: stackdriver.Breakpoint[][] = [ - [], - [{id: 'breakpoint-0', location: {path: 'foo.js', line: 18}}], - ] as stackdriver.Breakpoint[][]; - testsBreakpoints.forEach( - (breakpoints: stackdriver.Breakpoint[], index: number) => { - it('should pass test ' + index, done => { - const scope = nock(url) - .get( - api + '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true' - ) - .reply(200, {breakpoints}); - // TODO: Fix debuggee to actually implement Debuggee - const debuggee: Debuggee = {id: 'fake-debuggee'} as Debuggee; - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - // TODO: Determine if the response parameter should be used. - controller.listBreakpoints(debuggee, (err, response, result) => { - assert(!err, 'not expecting an error'); - assert.ok(result); - assert(result!.breakpoints, 'should have a breakpoints property'); - const bps = result!.breakpoints; - assert.deepStrictEqual(bps, breakpoints, 'breakpoints mismatch'); - scope.done(); - done(); - }); - }); - } - ); - }); +// // TODO: Fix this so that each element of the array is actually an +// // array of Breakpoints. +// const testsBreakpoints: stackdriver.Breakpoint[][] = [ +// [], +// [{id: 'breakpoint-0', location: {path: 'foo.js', line: 18}}], +// ] as stackdriver.Breakpoint[][]; +// testsBreakpoints.forEach( +// (breakpoints: stackdriver.Breakpoint[], index: number) => { +// it('should pass test ' + index, done => { +// const scope = nock(url) +// .get( +// api + '/debuggees/fake-debuggee/breakpoints?successOnTimeout=true' +// ) +// .reply(200, {breakpoints}); +// // TODO: Fix debuggee to actually implement Debuggee +// const debuggee: Debuggee = {id: 'fake-debuggee'} as Debuggee; +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// // TODO: Determine if the response parameter should be used. +// controller.listBreakpoints(debuggee, (err, response, result) => { +// assert(!err, 'not expecting an error'); +// assert.ok(result); +// assert(result!.breakpoints, 'should have a breakpoints property'); +// const bps = result!.breakpoints; +// assert.deepStrictEqual(bps, breakpoints, 'breakpoints mismatch'); +// scope.done(); +// done(); +// }); +// }); +// } +// ); +// }); - describe('updateBreakpoint', () => { - it('should PUT to server when a breakpoint is updated', done => { - // TODO: Fix breakpoint to actually Breakpoint - const breakpoint: stackdriver.Breakpoint = { - id: 'breakpoint-0', - location: {path: 'foo.js', line: 99}, - } as stackdriver.Breakpoint; - // A cast for the second argument to put() is necessary for nock 11 - // because the type definitions state that the second argument cannot - // be an Object even though the nock code itself seems to handle an - // Object. Further, the tests pass when using the cast. - // This issue is being tracked in the nock repo at - // https://github.com/nock/nock/issues/1731. - const scope = nock(url) - .put(api + '/debuggees/fake-debuggee/breakpoints/breakpoint-0', { - debuggeeId: 'fake-debuggee', - breakpoint, - } as {}) - .reply(200, { - kind: 'debugletcontroller#updateActiveBreakpointResponse', - }); - // TODO: Fix debuggee to actually implement Debuggee - const debuggee: Debuggee = {id: 'fake-debuggee'} as Debuggee; - const controller = new OnePlatformController( - fakeDebug, - DEFAULT_CONFIG, - logger - ); - controller.updateBreakpoint( - debuggee as Debuggee, - breakpoint, - (err, result) => { - assert(!err, 'not expecting an error'); - assert.strictEqual( - (result as {kind: {}}).kind, - 'debugletcontroller#updateActiveBreakpointResponse' - ); - scope.done(); - done(); - } - ); - }); - }); -}); +// describe('updateBreakpoint', () => { +// it('should PUT to server when a breakpoint is updated', done => { +// // TODO: Fix breakpoint to actually Breakpoint +// const breakpoint: stackdriver.Breakpoint = { +// id: 'breakpoint-0', +// location: {path: 'foo.js', line: 99}, +// } as stackdriver.Breakpoint; +// // A cast for the second argument to put() is necessary for nock 11 +// // because the type definitions state that the second argument cannot +// // be an Object even though the nock code itself seems to handle an +// // Object. Further, the tests pass when using the cast. +// // This issue is being tracked in the nock repo at +// // https://github.com/nock/nock/issues/1731. +// const scope = nock(url) +// .put(api + '/debuggees/fake-debuggee/breakpoints/breakpoint-0', { +// debuggeeId: 'fake-debuggee', +// breakpoint, +// } as {}) +// .reply(200, { +// kind: 'debugletcontroller#updateActiveBreakpointResponse', +// }); +// // TODO: Fix debuggee to actually implement Debuggee +// const debuggee: Debuggee = {id: 'fake-debuggee'} as Debuggee; +// const controller = new OnePlatformController( +// fakeDebug, +// DEFAULT_CONFIG, +// logger +// ); +// controller.updateBreakpoint( +// debuggee as Debuggee, +// breakpoint, +// (err, result) => { +// assert(!err, 'not expecting an error'); +// assert.strictEqual( +// (result as {kind: {}}).kind, +// 'debugletcontroller#updateActiveBreakpointResponse' +// ); +// scope.done(); +// done(); +// } +// ); +// }); +// }); +// }); diff --git a/test/test-debuglet.ts b/test/test-debuglet.ts index edb207c1..53e4ed4c 100644 --- a/test/test-debuglet.ts +++ b/test/test-debuglet.ts @@ -34,7 +34,6 @@ import { } from '../src/agent/debuglet'; import {ScanResults} from '../src/agent/io/scanner'; import * as extend from 'extend'; -import {Debug} from '../src/client/stackdriver/debug'; const DEBUGGEE_ID = 'bar'; const REGISTER_PATH = '/v2/controller/debuggees/register'; @@ -91,1725 +90,1724 @@ describe('CachedPromise', () => { }); }); -describe('Debuglet', () => { - describe('findFiles', () => { - const SOURCEMAP_DIR = path.join(__dirname, 'fixtures', 'sourcemaps'); - - it('throws an error for an invalid directory', async () => { - const config = extend({}, defaultConfig, { - workingDirectory: path.join(SOURCEMAP_DIR, '!INVALID'), - }); - let err: Error | null = null; - try { - await Debuglet.findFiles(config, 'fake-id'); - } catch (e) { - err = e as Error; - } - assert.ok(err); - }); - - it('finds the correct sourcemaps files', async () => { - const config = extend({}, defaultConfig, { - workingDirectory: SOURCEMAP_DIR, - }); - - const searchResults = await Debuglet.findFiles(config, 'fake-id'); - assert(searchResults.jsStats); - assert.strictEqual(Object.keys(searchResults.jsStats).length, 1); - assert(searchResults.jsStats[path.join(SOURCEMAP_DIR, 'js-file.js')]); - - assert.strictEqual(searchResults.mapFiles.length, 2); - const mapFiles = searchResults.mapFiles.sort(); - assert(mapFiles[0].endsWith('empty-source-map.js.map')); - assert(mapFiles[1].endsWith('js-map-file.js.map')); - }); - }); - - describe('setup', () => { - before(() => { - oldGP = process.env.GCLOUD_PROJECT; - }); - - after(() => { - process.env.GCLOUD_PROJECT = oldGP; - }); - - beforeEach(() => { - delete process.env.GCLOUD_PROJECT; - nocks.oauth2(); - }); - - afterEach(() => { - nock.cleanAll(); - }); - - it('should merge config correctly', () => { - const testValue = 2 * defaultConfig.capture.maxExpandFrames; - const config = {capture: {maxExpandFrames: testValue}}; - - // TODO: Fix this so that config does not have to be cast as - // DebugAgentConfig. - const mergedConfig = Debuglet.normalizeConfig_( - config as DebugAgentConfig - ); - // TODO: Debuglet.normalizeConfig_() expects 1 parameter but the original - // test code had zero arguments here. Determine which is correct. - const compareConfig = Debuglet.normalizeConfig_(null!); - // The actual config should be exactly defaultConfig with only - // maxExpandFrames adjusted. - compareConfig.capture.maxExpandFrames = testValue; - assert.deepStrictEqual(mergedConfig, compareConfig); - }); - - it('should not start when projectId is not available', done => { - const debug = new Debug({}, packageInfo); - - const savedGetProjectId = debug.authClient.getProjectId; - debug.authClient.getProjectId = () => { - return Promise.reject(new Error('no project id')); - }; - - const debuglet = new Debuglet(debug, defaultConfig); - - debuglet.once('initError', (err: Error) => { - assert.ok(err); - // no need to stop the debuggee. - debug.authClient.getProjectId = savedGetProjectId; - done(); - }); - debuglet.once('started', () => { - assert.fail('The debuglet should not have started'); - }); - debuglet.start(); - }); - - it('should give a useful error message when projectId is not available', done => { - const debug = new Debug({}, packageInfo); - - const savedGetProjectId = debug.authClient.getProjectId; - debug.authClient.getProjectId = () => { - return Promise.reject(new Error('no project id')); - }; - - const debuglet = new Debuglet(debug, defaultConfig); - - let message = ''; - const savedLoggerError = debuglet.logger.error; - debuglet.logger.error = (text: string) => { - message += text; - }; - - debuglet.once('initError', err => { - debug.authClient.getProjectId = savedGetProjectId; - debuglet.logger.error = savedLoggerError; - assert.ok(err); - assert(message.startsWith('The project ID could not be determined:')); - done(); - }); - debuglet.once('started', () => { - assert.fail('The debuglet should fail to start without a projectId'); - }); - debuglet.start(); - }); - - it('should not crash without project num', done => { - const debug = new Debug({}, packageInfo); - - const savedGetProjectId = debug.authClient.getProjectId; - debug.authClient.getProjectId = () => { - return Promise.reject(new Error('no project id')); - }; - - const debuglet = new Debuglet(debug, defaultConfig); - - debuglet.once('started', () => { - assert.fail('The debuglet should not have started'); - }); - debuglet.once('initError', () => { - debug.authClient.getProjectId = savedGetProjectId; - done(); - }); - debuglet.start(); - }); - - it('should use config.projectId', done => { - const projectId = '11020304f2934-a'; - const debug = new Debug( - {projectId, credentials: fakeCredentials}, - packageInfo - ); - - nocks.projectId('project-via-metadata'); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID}, - }); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - // TODO: Handle the case where debuglet.debuggee is undefined - assert.strictEqual((debuglet.debuggee as Debuggee).project, projectId); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should enable breakpoint canary when enableCanary is set', done => { - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - nocks.oauth2(); - - const config = debugletConfig(); - config.serviceContext.enableCanary = true; - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID}, - }); - - debuglet.once('registered', () => { - assert.strictEqual( - (debuglet.debuggee as Debuggee).canaryMode, - 'CANARY_MODE_ALWAYS_ENABLED' - ); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should have default resetV8DebuggerThreshold value', done => { - const debuglet = new Debuglet(new Debug({}, packageInfo), {}); - assert.strictEqual(debuglet.config.resetV8DebuggerThreshold, 30); - done(); - }); - - it('should overwrite resetV8DebuggerThreshold when available', done => { - const debuglet = new Debuglet(new Debug({}, packageInfo), { - resetV8DebuggerThreshold: 123, - }); - assert.strictEqual(debuglet.config.resetV8DebuggerThreshold, 123); - done(); - }); - - it('should not fail if files cannot be read', done => { - const MOCKED_DIRECTORY = process.cwd(); - const errors: Array<{filename: string; error: string}> = []; - for (let i = 1; i <= 2; i++) { - const filename = `cannot-read-${i}.js`; - const error = `EACCES: permission denied, open '${filename}'`; - errors.push({filename, error}); - } - const mockedDebuglet = proxyquire('../src/agent/debuglet', { - './io/scanner': { - scan: (baseDir: string, regex: RegExp, precomputedHash?: string) => { - assert.strictEqual(baseDir, MOCKED_DIRECTORY); - const results: ScanResults = { - errors: () => { - const map = new Map(); - for (const item of errors) { - map.set(item.filename, new Error(item.error)); - } - return map; - }, - all: () => { - return {}; - }, - selectStats: () => { - return {}; - }, - selectFiles: () => { - return []; - }, - hash: precomputedHash || 'fake-hash', - }; - return results; - }, - }, - }); - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - const config = extend({}, defaultConfig, { - workingDirectory: MOCKED_DIRECTORY, - }); - const debuglet = new mockedDebuglet.Debuglet(debug, config); - let text = ''; - debuglet.logger.warn = (s: string) => { - text += s; - }; - - debuglet.on('initError', () => { - assert.fail('It should not fail for files it cannot read'); - }); - - debuglet.once('started', () => { - for (const item of errors) { - const regex = new RegExp(item.error); - assert( - regex.test(text), - `Should warn that file '${item.filename}' cannot be read` - ); - } - debuglet.stop(); - done(); - }); - - debuglet.start(); - }); - - describe('environment variables', () => { - let env: NodeJS.ProcessEnv; - beforeEach(() => { - env = extend({}, process.env); - }); - afterEach(() => { - process.env = extend({}, env); - }); - - it('should use GCLOUD_PROJECT in lieu of config.projectId', done => { - process.env.GCLOUD_PROJECT = '11020304f2934-b'; - const debug = new Debug({credentials: fakeCredentials}, packageInfo); - - nocks.projectId('project-via-metadata'); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID}, - }); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - assert.strictEqual( - debuglet.debuggee!.project, - process.env.GCLOUD_PROJECT - ); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should use options.projectId in preference to the environment variable', done => { - process.env.GCLOUD_PROJECT = 'should-not-be-used'; - const debug = new Debug( - {projectId: 'project-via-options', credentials: fakeCredentials}, - packageInfo - ); - - nocks.projectId('project-via-metadata'); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID}, - }); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - assert.strictEqual(debuglet.debuggee!.project, 'project-via-options'); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should respect GCLOUD_DEBUG_LOGLEVEL', done => { - process.env.GCLOUD_PROJECT = '11020304f2934'; - process.env.GCLOUD_DEBUG_LOGLEVEL = '3'; - const debug = new Debug({credentials: fakeCredentials}, packageInfo); - - nocks.projectId('project-via-metadata'); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID}, - }); - - let buffer = ''; - const oldConsoleError = console.error; - console.error = (str: string) => { - buffer += str; - }; - - debuglet.once('registered', () => { - const logger = debuglet.logger; - const STRING1 = 'jjjjjjjjjjjjjjjjjfjfjfjf'; - const STRING2 = 'kkkkkkkfkfkfkfkfkkffkkkk'; - - logger.info(STRING1); - logger.debug(STRING2); - console.error = oldConsoleError; - - assert(buffer.indexOf(STRING1) !== -1); - assert(buffer.indexOf(STRING2) === -1); - - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should respect GAE_SERVICE and GAE_VERSION env. vars.', () => { - process.env.GAE_SERVICE = 'fake-gae-service'; - process.env.GAE_VERSION = 'fake-gae-version'; - const debug = new Debug({}, packageInfo); - const debuglet = new Debuglet(debug, defaultConfig); - assert.ok(debuglet.config); - assert.ok(debuglet.config.serviceContext); - assert.strictEqual( - debuglet.config.serviceContext.service, - 'fake-gae-service' - ); - assert.strictEqual( - debuglet.config.serviceContext.version, - 'fake-gae-version' - ); - }); - - it('should respect GAE_MODULE_NAME and GAE_MODULE_VERSION env. vars.', () => { - process.env.GAE_MODULE_NAME = 'fake-gae-service'; - process.env.GAE_MODULE_VERSION = 'fake-gae-version'; - const debug = new Debug({}, packageInfo); - const debuglet = new Debuglet(debug, defaultConfig); - assert.ok(debuglet.config); - assert.ok(debuglet.config.serviceContext); - assert.strictEqual( - debuglet.config.serviceContext.service, - 'fake-gae-service' - ); - assert.strictEqual( - debuglet.config.serviceContext.version, - 'fake-gae-version' - ); - }); - - it('should respect K_SERVICE and K_REVISION env. vars.', () => { - process.env.K_SERVICE = 'fake-cloudrun-service'; - process.env.K_REVISION = 'fake-cloudrun-version'; - const debug = new Debug({}, packageInfo); - const debuglet = new Debuglet(debug, defaultConfig); - assert.ok(debuglet.config); - assert.ok(debuglet.config.serviceContext); - assert.strictEqual( - debuglet.config.serviceContext.service, - 'fake-cloudrun-service' - ); - assert.strictEqual( - debuglet.config.serviceContext.version, - 'fake-cloudrun-version' - ); - }); - - it('should respect FUNCTION_NAME env. var.', () => { - process.env.FUNCTION_NAME = 'fake-fn-name'; - const debug = new Debug({}, packageInfo); - const debuglet = new Debuglet(debug, defaultConfig); - assert.ok(debuglet.config); - assert.ok(debuglet.config.serviceContext); - assert.strictEqual( - debuglet.config.serviceContext.service, - 'fake-fn-name' - ); - assert.strictEqual( - debuglet.config.serviceContext.version, - 'unversioned' - ); - }); - - it('should prefer new flex vars over GAE_MODULE_*', () => { - process.env.GAE_MODULE_NAME = 'fake-gae-module'; - process.env.GAE_MODULE_VERSION = 'fake-gae-module-version'; - process.env.GAE_SERVICE = 'fake-gae-service'; - process.env.GAE_VERSION = 'fake-gae-version'; - const debug = new Debug({}, packageInfo); - const debuglet = new Debuglet(debug, defaultConfig); - assert.ok(debuglet.config); - assert.ok(debuglet.config.serviceContext); - assert.strictEqual( - debuglet.config.serviceContext.service, - 'fake-gae-service' - ); - assert.strictEqual( - debuglet.config.serviceContext.version, - 'fake-gae-version' - ); - }); - - it('should respect GAE_DEPLOYMENT_ID env. var. when available', () => { - process.env.GAE_DEPLOYMENT_ID = 'some deployment id'; - delete process.env.GAE_MINOR_VERSION; - const debug = new Debug({}, packageInfo); - const debuglet = new Debuglet(debug, defaultConfig); - assert.ok(debuglet.config); - assert.ok(debuglet.config.serviceContext); - assert.strictEqual( - debuglet.config.serviceContext.minorVersion_, - 'some deployment id' - ); - }); - - it('should respect GAE_MINOR_VERSION env. var. when available', () => { - delete process.env.GAE_DEPLOYMENT_ID; - process.env.GAE_MINOR_VERSION = 'some minor version'; - const debug = new Debug({}, packageInfo); - const debuglet = new Debuglet(debug, defaultConfig); - assert.ok(debuglet.config); - assert.ok(debuglet.config.serviceContext); - assert.strictEqual( - debuglet.config.serviceContext.minorVersion_, - 'some minor version' - ); - }); - - it('should prefer GAE_DEPLOYMENT_ID over GAE_MINOR_VERSION', () => { - process.env.GAE_DEPLOYMENT_ID = 'some deployment id'; - process.env.GAE_MINOR_VERSION = 'some minor version'; - const debug = new Debug({}, packageInfo); - const debuglet = new Debuglet(debug, defaultConfig); - assert.ok(debuglet.config); - assert.ok(debuglet.config.serviceContext); - assert.strictEqual( - debuglet.config.serviceContext.minorVersion_, - 'some deployment id' - ); - }); - - it('should not have minorVersion unless enviroment provides it', () => { - const debug = new Debug({}, packageInfo); - const debuglet = new Debuglet(debug, defaultConfig); - assert.ok(debuglet.config); - assert.ok(debuglet.config.serviceContext); - // TODO: IMPORTANT: It appears that this test is incorrect as it - // is. That is, if minorVersion is replaced with the - // correctly named minorVersion_, then the test fails. - // Resolve this. - assert.strictEqual( - undefined, - ( - debuglet.config.serviceContext as { - minorVersion: {}; - } - ).minorVersion - ); - }); - - it('should not provide minorversion upon registration on non flex', done => { - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH, (body: {debuggee: Debuggee}) => { - assert.strictEqual(undefined, body.debuggee.labels!.minorversion); - return true; - }) - .once() - .reply(200, {debuggee: {id: DEBUGGEE_ID}}); - - // TODO: Determine if the id parameter should be used. - debuglet.once('registered', () => { - debuglet.stop(); - scope.done(); - done(); - }); - debuglet.start(); - }); - }); - - it('should retry on failed registration', function (done) { - this.timeout(10000); - const debug = new Debug( - {projectId: '11020304f2934', credentials: fakeCredentials}, - packageInfo - ); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(404) - .post(REGISTER_PATH) - .reply(509) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it("should error if a package.json doesn't exist", done => { - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - const config = extend({}, defaultConfig, { - workingDirectory: __dirname, - forceNewAgent_: true, - }); - const debuglet = new Debuglet(debug, config); - - debuglet.once('initError', (err: Error) => { - assert(err); - done(); - }); - - debuglet.start(); - }); - - it('should by default error when workingDirectory is a root directory with a package.json', done => { - const debug = new Debug({}, packageInfo); - /* - * `path.sep` represents a root directory on both Windows and Unix. - * On Windows, `path.sep` resolves to the current drive. - * - * That is, after opening a command prompt in Windows, relative to the - * drive C: and starting the Node REPL, the value of `path.sep` - * represents `C:\\'. - * - * If `D:` is entered at the prompt to switch to the D: drive before - * starting the Node REPL, `path.sep` represents `D:\\`. - */ - const root = path.sep; - const mockedDebuglet = proxyquire('../src/agent/debuglet', { - /* - * Mock the 'fs' module to verify that if the root directory is used, - * and the root directory is reported to contain a `package.json` - * file, then the agent still produces an `initError` when the - * working directory is the root directory. - */ - fs: { - stat: ( - filepath: string | Buffer, - cb: (err: Error | null, stats: {}) => void - ) => { - if (filepath === path.join(root, 'package.json')) { - // The key point is that looking for `package.json` in the - // root directory does not cause an error. - return cb(null, {}); - } - fs.stat(filepath, cb); - }, - }, - }); - const config = extend({}, defaultConfig, {workingDirectory: root}); - const debuglet = new mockedDebuglet.Debuglet(debug, config); - let text = ''; - debuglet.logger.error = (str: string) => { - text += str; - }; - - debuglet.on('initError', (err: Error) => { - const errorMessage = - 'The working directory is a root ' + - 'directory. Disabling to avoid a scan of the entire filesystem ' + - 'for JavaScript files. Use config `allowRootAsWorkingDirectory` ' + - 'if you really want to do this.'; - assert.ok(err); - assert.strictEqual(err.message, errorMessage); - assert.ok(text.includes(errorMessage)); - done(); - }); - - debuglet.once('started', () => { - assert.fail('Should not start if workingDirectory is a root directory'); - }); - - debuglet.start(); - }); - - it('should be able to force the workingDirectory to be a root directory', done => { - const root = path.sep; - // Act like the root directory contains a `package.json` file - const mockedDebuglet = proxyquire('../src/agent/debuglet', { - fs: { - stat: ( - filepath: string | Buffer, - cb: (err: Error | null, stats: {}) => void - ) => { - if (filepath === path.join(root, 'package.json')) { - return cb(null, {}); - } - fs.stat(filepath, cb); - }, - }, - }); - - // Don't actually scan the entire filesystem. Act like the filesystem - // is empty. - mockedDebuglet.Debuglet.findFiles = ( - config: ResolvedDebugAgentConfig - ): Promise => { - const baseDir = config.workingDirectory; - assert.strictEqual(baseDir, root); - return Promise.resolve({ - jsStats: {}, - mapFiles: [], - errors: new Map(), - hash: 'fake-hash', - }); - }; - - // No need to restore `findFiles` because we are modifying a - // mocked version of `Debuglet` not `Debuglet` itself. - - const config = extend({}, defaultConfig, { - workingDirectory: root, - allowRootAsWorkingDirectory: true, - }); - - const debug = new Debug({}, packageInfo); - - // Act like the debuglet can get a project id - debug.authClient.getProjectId = async () => 'some-project-id'; - - const debuglet = new mockedDebuglet.Debuglet(debug, config); - - debuglet.on('initError', (err: Error) => { - assert.ifError(err); - done(); - }); - - debuglet.once('started', () => { - debuglet.stop(); - done(); - }); - - debuglet.start(); - }); - - it('should register successfully otherwise', done => { - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - nocks.oauth2(); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID}, - }); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should stop successfully even if stop is called quickly', () => { - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - nocks.oauth2(); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - - debuglet.start(); - debuglet.stop(); - }); - - it('should register successfully even if stop was called first', done => { - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - nocks.oauth2(); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID}, - }); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.stop(); - - debuglet.start(); - }); - - it('should attempt to retrieve cluster name if needed', done => { - const savedRunningOnGCP = Debuglet.runningOnGCP; - Debuglet.runningOnGCP = () => { - return Promise.resolve(true); - }; - const clusterScope = nock(gcpMetadata.HOST_ADDRESS) - .get('/computeMetadata/v1/instance/attributes/cluster-name') - .once() - .reply(200, 'cluster-name-from-metadata'); - - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - nocks.oauth2(); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID}, - }); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - debuglet.stop(); - clusterScope.done(); - scope.done(); - Debuglet.runningOnGCP = savedRunningOnGCP; - done(); - }); - - debuglet.start(); - }); - - it('should attempt to retreive region correctly if needed', done => { - const savedGetPlatform = Debuglet.getPlatform; - Debuglet.getPlatform = () => Platforms.CLOUD_FUNCTION; - - const clusterScope = nock(gcpMetadata.HOST_ADDRESS) - .get('/computeMetadata/v1/instance/region') - .once() - .reply(200, '123/456/region_name', gcpMetadata.HEADERS); - - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - nocks.oauth2(); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID}, - }); - - debuglet.once('registered', () => { - Debuglet.getPlatform = savedGetPlatform; - assert.strictEqual( - (debuglet.debuggee as Debuggee).labels?.region, - 'region_name' - ); - debuglet.stop(); - clusterScope.done(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should continue to register when could not get region', done => { - const savedGetPlatform = Debuglet.getPlatform; - Debuglet.getPlatform = () => Platforms.CLOUD_FUNCTION; - - const clusterScope = nock(gcpMetadata.HOST_ADDRESS) - .get('/computeMetadata/v1/instance/region') - .once() - .reply(400); - - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - nocks.oauth2(); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID}, - }); - - debuglet.once('registered', () => { - Debuglet.getPlatform = savedGetPlatform; - debuglet.stop(); - clusterScope.done(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should pass config source context to api', done => { - const REPO_URL = - 'https://github.com/non-existent-users/non-existend-repo'; - const REVISION_ID = '5'; - - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - const config = debugletConfig({ - sourceContext: {url: REPO_URL, revisionId: REVISION_ID}, - }); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH, (body: {debuggee: Debuggee}) => { - const context = body.debuggee.sourceContexts![0]; - return ( - context && - context.url === REPO_URL && - context.revisionId === REVISION_ID - ); - }) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should pass source context to api if present', done => { - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - const old = Debuglet.getSourceContextFromFile; - Debuglet.getSourceContextFromFile = async () => { - return {a: 5 as {} as string}; - }; - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH, (body: {debuggee: Debuggee}) => { - const context = body.debuggee.sourceContexts![0]; - return context && context.a === 5; - }) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}); - - debuglet.once('registered', (id: string) => { - Debuglet.getSourceContextFromFile = old; - assert.strictEqual(id, DEBUGGEE_ID); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should prefer config source context to file', done => { - const REPO_URL = - 'https://github.com/non-existent-users/non-existend-repo'; - const REVISION_ID = '5'; - - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - const old = Debuglet.getSourceContextFromFile; - Debuglet.getSourceContextFromFile = async () => { - return {a: 5 as {} as string}; - }; - - const config = debugletConfig({ - sourceContext: {url: REPO_URL, revisionId: REVISION_ID}, - }); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH, (body: {debuggee: Debuggee}) => { - const context = body.debuggee.sourceContexts![0]; - return ( - context && - context.url === REPO_URL && - context.revisionId === REVISION_ID - ); - }) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}); - - debuglet.once('registered', (id: string) => { - Debuglet.getSourceContextFromFile = old; - assert.strictEqual(id, DEBUGGEE_ID); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should de-activate when the server responds with isDisabled', function (done) { - this.timeout(4000); - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, { - debuggee: {id: DEBUGGEE_ID, isDisabled: true}, - }); - - debuglet.once('remotelyDisabled', () => { - assert.ok(!debuglet.fetcherActive); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should retry after a isDisabled request', function (done) { - this.timeout(4000); - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID, isDisabled: true}}) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}); - - let gotDisabled = false; - debuglet.once('remotelyDisabled', () => { - assert.ok(!debuglet.fetcherActive); - gotDisabled = true; - }); - - debuglet.once('registered', (id: string) => { - assert.ok(gotDisabled); - assert.strictEqual(id, DEBUGGEE_ID); - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should re-register when registration expires', done => { - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(404) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}); - - debuglet.once('registered', (id1: string) => { - assert.strictEqual(id1, DEBUGGEE_ID); - debuglet.once('registered', (id2: string) => { - assert.strictEqual(id2, DEBUGGEE_ID); - debuglet.stop(); - scope.done(); - done(); - }); - }); - - debuglet.start(); - }); - - it('should fetch and add breakpoints', function (done) { - this.timeout(2000); - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(200, {breakpoints: [bp]}); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - setTimeout(() => { - assert.deepStrictEqual(debuglet.activeBreakpointMap.test, bp); - debuglet.stop(); - scope.done(); - done(); - }, 1000); - }); - - debuglet.start(); - }); - - it('should have breakpoints fetched when promise is resolved', function (done) { - this.timeout(2000); - const breakpoint: stackdriver.Breakpoint = { - id: 'test1', - action: 'CAPTURE', - location: {path: 'build/test/fixtures/foo.js', line: 2}, - } as stackdriver.Breakpoint; - - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(200, {breakpoints: [breakpoint]}); - const debugPromise = debuglet.isReadyManager.isReady(); - debuglet.once('registered', () => { - debugPromise.then(() => { - // Once debugPromise is resolved, debuggee must be registered. - assert(debuglet.debuggee); - setTimeout(() => { - assert.deepStrictEqual( - debuglet.activeBreakpointMap.test1, - breakpoint - ); - debuglet.activeBreakpointMap = {}; - debuglet.stop(); - scope.done(); - done(); - }, 1000); - }); - }); - debuglet.start(); - }); - - it('should resolve breakpointFetched promise when registration expires', function (done) { - this.timeout(2000); - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - // const debuglet = new Debuglet(debug, defaultConfig); - // const scope = nock(API) - /// this change to a unique scope per test causes - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(404); // signal re-registration. - const debugPromise = debuglet.isReadyManager.isReady(); - debugPromise.then(() => { - debuglet.stop(); - scope.done(); - done(); - }); - - debuglet.start(); - }); - - it('should reject breakpoints with conditions when allowExpressions=false', function (done) { - this.timeout(2000); - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - const config = debugletConfig({allowExpressions: false}); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(200, { - breakpoints: [ - { - id: 'test', - action: 'CAPTURE', - condition: 'x === 5', - location: {path: 'fixtures/foo.js', line: 2}, - }, - ], - }) - .put( - BPS_PATH + '/test', - verifyBreakpointRejection.bind(null, EXPRESSIONS_REGEX) - ) - .reply(200); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - setTimeout(() => { - assert.ok(!debuglet.activeBreakpointMap.test); - debuglet.stop(); - debuglet.config.allowExpressions = true; - scope.done(); - done(); - }, 1000); - }); - - debuglet.start(); - }); - - it('should reject breakpoints with expressions when allowExpressions=false', function (done) { - this.timeout(2000); - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - const config = debugletConfig({allowExpressions: false}); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(200, { - breakpoints: [ - { - id: 'test', - action: 'CAPTURE', - expressions: ['x'], - location: {path: 'fixtures/foo.js', line: 2}, - }, - ], - }) - .put( - BPS_PATH + '/test', - verifyBreakpointRejection.bind(null, EXPRESSIONS_REGEX) - ) - .reply(200); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - setTimeout(() => { - assert.ok(!debuglet.activeBreakpointMap.test); - debuglet.stop(); - debuglet.config.allowExpressions = true; - scope.done(); - done(); - }, 1000); - }); - - debuglet.start(); - }); - - it('should re-fetch breakpoints on error', function (done) { - this.timeout(6000); - - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - const config = debugletConfig(); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(404) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(200, {waitExpired: true}) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(200, {breakpoints: [bp, errorBp]}) - .put( - BPS_PATH + '/' + errorBp.id, - (body: {breakpoint: stackdriver.Breakpoint}) => { - const status = body.breakpoint.status; - return ( - status!.isError && - status!.description.format.indexOf('actions are CAPTURE') !== -1 - ); - } - ) - .reply(200); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - setTimeout(() => { - assert.deepStrictEqual(debuglet.activeBreakpointMap.test, bp); - assert(!debuglet.activeBreakpointMap.testLog); - debuglet.stop(); - scope.done(); - done(); - }, 1000); - }); - - debuglet.start(); - }); - - it('should expire stale breakpoints', function (done) { - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - this.timeout(6000); - - const config = debugletConfig({ - breakpointExpirationSec: 1, - forceNewAgent_: true, - }); - const debuglet = new Debuglet(debug, config); - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(200, {breakpoints: [bp]}) - .put( - BPS_PATH + '/test', - (body: {breakpoint: stackdriver.Breakpoint}) => { - const status = body.breakpoint.status; - return ( - status!.description.format === 'The snapshot has expired' && - status!.refersTo === 'BREAKPOINT_AGE' - ); - } - ) - .reply(200); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - setTimeout(() => { - assert.deepStrictEqual(debuglet.activeBreakpointMap.test, bp); - setTimeout(() => { - assert(!debuglet.activeBreakpointMap.test); - debuglet.stop(); - scope.done(); - done(); - }, 1100); - }, 500); - }); - - debuglet.start(); - }); - - // This test catches regressions in a bug where the agent would - // re-schedule an already expired breakpoint to expire if the - // server listed the breakpoint as active (which it may do depending - // on how quickly the expiry is processed). - // The test expires a breakpoint and then has the api respond with - // the breakpoint listed as active. It validates that the breakpoint - // is only expired with the server once. - it('should not update expired breakpoints', function (done) { - const debug = new Debug( - {projectId: 'fake-project', credentials: fakeCredentials}, - packageInfo - ); - - this.timeout(6000); - - const config = debugletConfig({ - breakpointExpirationSec: 1, - breakpointUpdateIntervalSec: 1, - forceNewAgent_: true, - }); - const debuglet = new Debuglet(debug, config); - let unexpectedUpdate = false; - const scope = nock(config.apiUrl) - .post(REGISTER_PATH) - .reply(200, {debuggee: {id: DEBUGGEE_ID}}) - .get(BPS_PATH + '?successOnTimeout=true') - .reply(200, {breakpoints: [bp]}) - .put( - BPS_PATH + '/test', - (body: {breakpoint: stackdriver.Breakpoint}) => { - return ( - body.breakpoint.status!.description.format === - 'The snapshot has expired' - ); - } - ) - .reply(200) - .get(BPS_PATH + '?successOnTimeout=true') - .times(4) - .reply(200, {breakpoints: [bp]}); - - // Get ready to fail the test if any additional updates come through. - nock.emitter.on('no match', req => { - if (req.path.startsWith(BPS_PATH) && req.method === 'PUT') { - unexpectedUpdate = true; - } - }); - - debuglet.once('registered', (id: string) => { - assert.strictEqual(id, DEBUGGEE_ID); - setTimeout(() => { - assert.deepStrictEqual(debuglet.activeBreakpointMap.test, bp); - setTimeout(() => { - assert(!debuglet.activeBreakpointMap.test); - assert(!unexpectedUpdate, 'unexpected update request'); - debuglet.stop(); - scope.done(); - done(); - }, 4500); - }, 500); - }); - - debuglet.start(); - }); - }); - - describe('map subtract', () => { - it('should be correct', () => { - const a = {a: 1, b: 2}; - const b = {a: 1}; - assert.deepStrictEqual(Debuglet.mapSubtract(a, b), [2]); - assert.deepStrictEqual(Debuglet.mapSubtract(b, a), []); - assert.deepStrictEqual(Debuglet.mapSubtract(a, {}), [1, 2]); - assert.deepStrictEqual(Debuglet.mapSubtract({}, b), []); - }); - }); - - describe('format', () => { - it('should be correct', () => { - // TODO: Determine if Debuglet.format() should allow a number[] - // or if only string[] should be allowed. - assert.deepStrictEqual( - Debuglet.format('hi', [5] as {} as string[]), - 'hi' - ); - assert.deepStrictEqual( - Debuglet.format('hi $0', [5] as {} as string[]), - 'hi 5' - ); - assert.deepStrictEqual( - Debuglet.format('hi $0 $1', [5, 'there'] as {} as string[]), - 'hi 5 there' - ); - assert.deepStrictEqual( - Debuglet.format('hi $0 $1', [5] as {} as string[]), - 'hi 5 $1' - ); - assert.deepStrictEqual( - Debuglet.format('hi $0 $1 $0', [5] as {} as string[]), - 'hi 5 $1 5' - ); - assert.deepStrictEqual( - Debuglet.format('hi $$', [5] as {} as string[]), - 'hi $' - ); - assert.deepStrictEqual( - Debuglet.format('hi $$0', [5] as {} as string[]), - 'hi $0' - ); - assert.deepStrictEqual( - Debuglet.format('hi $00', [5] as {} as string[]), - 'hi 50' - ); - assert.deepStrictEqual( - Debuglet.format('hi $0', ['$1', 5] as {} as string[]), - 'hi $1' - ); - assert.deepStrictEqual( - Debuglet.format('hi $11', [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 'a', - 'b', - 'c', - 'd', - ] as {} as string[]), - 'hi b' - ); - }); - }); - - describe('createDebuggee', () => { - it('should have sensible labels', () => { - const debuggee = Debuglet.createDebuggee( - 'some project', - 'id', - {service: 'some-service', version: 'production'}, - {}, - false, - packageInfo, - Platforms.DEFAULT - ); - assert.ok(debuggee); - assert.ok(debuggee.labels); - assert.strictEqual(debuggee.labels!.module, 'some-service'); - assert.strictEqual(debuggee.labels!.version, 'production'); - }); - - it('should not add a module label when service is default', () => { - const debuggee = Debuglet.createDebuggee( - 'fancy-project', - 'very-unique', - {service: 'default', version: 'yellow.5'}, - {}, - false, - packageInfo, - Platforms.DEFAULT - ); - assert.ok(debuggee); - assert.ok(debuggee.labels); - assert.strictEqual(debuggee.labels!.module, undefined); - assert.strictEqual(debuggee.labels!.version, 'yellow.5'); - }); - - it('should have an error statusMessage with the appropriate arg', () => { - const debuggee = Debuglet.createDebuggee( - 'a', - 'b', - {}, - {}, - false, - packageInfo, - Platforms.DEFAULT, - undefined, - 'Some Error Message' - ); - assert.ok(debuggee); - assert.ok(debuggee.statusMessage); - }); - - it('should be in CANARY_MODE_DEFAULT_ENABLED canaryMode', () => { - const debuggee = Debuglet.createDebuggee( - 'some project', - 'id', - {enableCanary: true, allowCanaryOverride: true}, - {}, - false, - packageInfo, - Platforms.DEFAULT - ); - assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_ENABLED'); - }); - - it('should be in CANARY_MODE_ALWAYS_ENABLED canaryMode', () => { - const debuggee = Debuglet.createDebuggee( - 'some project', - 'id', - {enableCanary: true, allowCanaryOverride: false}, - {}, - false, - packageInfo, - Platforms.DEFAULT - ); - assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_ENABLED'); - }); - - it('should be in CANARY_MODE_DEFAULT_DISABLED canaryMode', () => { - const debuggee = Debuglet.createDebuggee( - 'some project', - 'id', - {enableCanary: false, allowCanaryOverride: true}, - {}, - false, - packageInfo, - Platforms.DEFAULT - ); - assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_DISABLED'); - }); - - it('should be in CANARY_MODE_ALWAYS_DISABLED canaryMode', () => { - const debuggee = Debuglet.createDebuggee( - 'some project', - 'id', - {enableCanary: false, allowCanaryOverride: false}, - {}, - false, - packageInfo, - Platforms.DEFAULT - ); - assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_DISABLED'); - }); - }); - - describe('getPlatform', () => { - it('should correctly identify default platform.', () => { - assert.ok(Debuglet.getPlatform() === Platforms.DEFAULT); - }); - - it('should correctly identify GCF (legacy) platform.', () => { - // GCF sets this env var on older runtimes. - process.env.FUNCTION_NAME = 'mock'; - assert.ok(Debuglet.getPlatform() === Platforms.CLOUD_FUNCTION); - delete process.env.FUNCTION_NAME; // Don't contaminate test environment. - }); - - it('should correctly identify GCF (modern) platform.', () => { - // GCF sets this env var on modern runtimes. - process.env.FUNCTION_TARGET = 'mock'; - assert.ok(Debuglet.getPlatform() === Platforms.CLOUD_FUNCTION); - delete process.env.FUNCTION_TARGET; // Don't contaminate test environment. - }); - }); - - describe('getRegion', () => { - it('should return function region for older GCF runtime', async () => { - process.env.FUNCTION_REGION = 'mock'; - - assert.ok((await Debuglet.getRegion()) === 'mock'); - - delete process.env.FUNCTION_REGION; - }); - - it('should return region for newer GCF runtime', async () => { - const clusterScope = nock(gcpMetadata.HOST_ADDRESS) - .get('/computeMetadata/v1/instance/region') - .once() - .reply(200, '123/456/region_name', gcpMetadata.HEADERS); - - assert.ok((await Debuglet.getRegion()) === 'region_name'); - - clusterScope.done(); - }); - - it('should return undefined when cannot get region metadata', async () => { - const clusterScope = nock(gcpMetadata.HOST_ADDRESS) - .get('/computeMetadata/v1/instance/region') - .once() - .reply(400); - - assert.ok((await Debuglet.getRegion()) === undefined); - - clusterScope.done(); - }); - }); - - describe('_createUniquifier', () => { - it('should create a unique string', () => { - const fn = Debuglet._createUniquifier; - - const desc = 'description'; - const version = 'version'; - const uid = 'uid'; - const sourceContext = {git: 'something'}; - const labels = {key: 'value'}; - - const u1 = fn(desc, version, uid, sourceContext, labels); - - assert.strictEqual(fn(desc, version, uid, sourceContext, labels), u1); - - assert.notStrictEqual( - fn('foo', version, uid, sourceContext, labels), - u1, - 'changing the description should change the result' - ); - assert.notStrictEqual( - fn(desc, '1.2', uid, sourceContext, labels), - u1, - 'changing the version should change the result' - ); - assert.notStrictEqual( - fn(desc, version, '5', sourceContext, labels), - u1, - 'changing the description should change the result' - ); - assert.notStrictEqual( - fn(desc, version, uid, {git: 'blah'}, labels), - u1, - 'changing the sourceContext should change the result' - ); - assert.notStrictEqual( - fn(desc, version, uid, sourceContext, {key1: 'value2'}), - u1, - 'changing the labels should change the result' - ); - }); - }); -}); +// TODO: Write Debuglet tests that work against the Firebase backend. + +// describe('Debuglet', () => { +// describe('findFiles', () => { +// const SOURCEMAP_DIR = path.join(__dirname, 'fixtures', 'sourcemaps'); + +// it('throws an error for an invalid directory', async () => { +// const config = extend({}, defaultConfig, { +// workingDirectory: path.join(SOURCEMAP_DIR, '!INVALID'), +// }); +// let err: Error | null = null; +// try { +// await Debuglet.findFiles(config, 'fake-id'); +// } catch (e) { +// err = e as Error; +// } +// assert.ok(err); +// }); + +// it('finds the correct sourcemaps files', async () => { +// const config = extend({}, defaultConfig, { +// workingDirectory: SOURCEMAP_DIR, +// }); + +// const searchResults = await Debuglet.findFiles(config, 'fake-id'); +// assert(searchResults.jsStats); +// assert.strictEqual(Object.keys(searchResults.jsStats).length, 1); +// assert(searchResults.jsStats[path.join(SOURCEMAP_DIR, 'js-file.js')]); + +// assert.strictEqual(searchResults.mapFiles.length, 2); +// const mapFiles = searchResults.mapFiles.sort(); +// assert(mapFiles[0].endsWith('empty-source-map.js.map')); +// assert(mapFiles[1].endsWith('js-map-file.js.map')); +// }); +// }); + +// describe('setup', () => { +// before(() => { +// oldGP = process.env.GCLOUD_PROJECT; +// }); + +// after(() => { +// process.env.GCLOUD_PROJECT = oldGP; +// }); + +// beforeEach(() => { +// delete process.env.GCLOUD_PROJECT; +// nocks.oauth2(); +// }); + +// afterEach(() => { +// nock.cleanAll(); +// }); + +// it('should merge config correctly', () => { +// const testValue = 2 * defaultConfig.capture.maxExpandFrames; +// const config = {capture: {maxExpandFrames: testValue}}; + +// // TODO: Fix this so that config does not have to be cast as +// // DebugAgentConfig. +// const mergedConfig = Debuglet.normalizeConfig_( +// config as DebugAgentConfig +// ); +// // TODO: Debuglet.normalizeConfig_() expects 1 parameter but the original +// // test code had zero arguments here. Determine which is correct. +// const compareConfig = Debuglet.normalizeConfig_(null!); +// // The actual config should be exactly defaultConfig with only +// // maxExpandFrames adjusted. +// compareConfig.capture.maxExpandFrames = testValue; +// assert.deepStrictEqual(mergedConfig, compareConfig); +// }); + +// it('should not start when projectId is not available', done => { +// const debug = new Debug({}, packageInfo); + +// const savedGetProjectId = debug.authClient.getProjectId; +// debug.authClient.getProjectId = () => { +// return Promise.reject(new Error('no project id')); +// }; + +// const debuglet = new Debuglet(debug, defaultConfig); + +// debuglet.once('initError', (err: Error) => { +// assert.ok(err); +// // no need to stop the debuggee. +// debug.authClient.getProjectId = savedGetProjectId; +// done(); +// }); +// debuglet.once('started', () => { +// assert.fail('The debuglet should not have started'); +// }); +// debuglet.start(); +// }); + +// it('should give a useful error message when projectId is not available', done => { +// const debug = new Debug({}, packageInfo); + +// const savedGetProjectId = debug.authClient.getProjectId; +// debug.authClient.getProjectId = () => { +// return Promise.reject(new Error('no project id')); +// }; + +// const debuglet = new Debuglet(debug, defaultConfig); + +// let message = ''; +// const savedLoggerError = debuglet.logger.error; +// debuglet.logger.error = (text: string) => { +// message += text; +// }; + +// debuglet.once('initError', err => { +// debug.authClient.getProjectId = savedGetProjectId; +// debuglet.logger.error = savedLoggerError; +// assert.ok(err); +// assert(message.startsWith('The project ID could not be determined:')); +// done(); +// }); +// debuglet.once('started', () => { +// assert.fail('The debuglet should fail to start without a projectId'); +// }); +// debuglet.start(); +// }); + +// it('should not crash without project num', done => { +// const debug = new Debug({}, packageInfo); + +// const savedGetProjectId = debug.authClient.getProjectId; +// debug.authClient.getProjectId = () => { +// return Promise.reject(new Error('no project id')); +// }; + +// const debuglet = new Debuglet(debug, defaultConfig); + +// debuglet.once('started', () => { +// assert.fail('The debuglet should not have started'); +// }); +// debuglet.once('initError', () => { +// debug.authClient.getProjectId = savedGetProjectId; +// done(); +// }); +// debuglet.start(); +// }); + +// it('should use config.projectId', done => { +// const projectId = '11020304f2934-a'; +// const debug = new Debug( +// {projectId, credentials: fakeCredentials}, +// packageInfo +// ); + +// nocks.projectId('project-via-metadata'); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID}, +// }); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// // TODO: Handle the case where debuglet.debuggee is undefined +// assert.strictEqual((debuglet.debuggee as Debuggee).project, projectId); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should enable breakpoint canary when enableCanary is set', done => { +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); +// nocks.oauth2(); + +// const config = debugletConfig(); +// config.serviceContext.enableCanary = true; +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID}, +// }); + +// debuglet.once('registered', () => { +// assert.strictEqual( +// (debuglet.debuggee as Debuggee).canaryMode, +// 'CANARY_MODE_ALWAYS_ENABLED' +// ); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should have default resetV8DebuggerThreshold value', done => { +// const debuglet = new Debuglet(new Debug({}, packageInfo), {}); +// assert.strictEqual(debuglet.config.resetV8DebuggerThreshold, 30); +// done(); +// }); + +// it('should overwrite resetV8DebuggerThreshold when available', done => { +// const debuglet = new Debuglet(new Debug({}, packageInfo), { +// resetV8DebuggerThreshold: 123, +// }); +// assert.strictEqual(debuglet.config.resetV8DebuggerThreshold, 123); +// done(); +// }); + +// it('should not fail if files cannot be read', done => { +// const MOCKED_DIRECTORY = process.cwd(); +// const errors: Array<{filename: string; error: string}> = []; +// for (let i = 1; i <= 2; i++) { +// const filename = `cannot-read-${i}.js`; +// const error = `EACCES: permission denied, open '${filename}'`; +// errors.push({filename, error}); +// } +// const mockedDebuglet = proxyquire('../src/agent/debuglet', { +// './io/scanner': { +// scan: (baseDir: string, regex: RegExp, precomputedHash?: string) => { +// assert.strictEqual(baseDir, MOCKED_DIRECTORY); +// const results: ScanResults = { +// errors: () => { +// const map = new Map(); +// for (const item of errors) { +// map.set(item.filename, new Error(item.error)); +// } +// return map; +// }, +// all: () => { +// return {}; +// }, +// selectStats: () => { +// return {}; +// }, +// selectFiles: () => { +// return []; +// }, +// hash: precomputedHash || 'fake-hash', +// }; +// return results; +// }, +// }, +// }); +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); +// const config = extend({}, defaultConfig, { +// workingDirectory: MOCKED_DIRECTORY, +// }); +// const debuglet = new mockedDebuglet.Debuglet(debug, config); +// let text = ''; +// debuglet.logger.warn = (s: string) => { +// text += s; +// }; + +// debuglet.on('initError', () => { +// assert.fail('It should not fail for files it cannot read'); +// }); + +// debuglet.once('started', () => { +// for (const item of errors) { +// const regex = new RegExp(item.error); +// assert( +// regex.test(text), +// `Should warn that file '${item.filename}' cannot be read` +// ); +// } +// debuglet.stop(); +// done(); +// }); + +// debuglet.start(); +// }); + +// describe('environment variables', () => { +// let env: NodeJS.ProcessEnv; +// beforeEach(() => { +// env = extend({}, process.env); +// }); +// afterEach(() => { +// process.env = extend({}, env); +// }); + +// it('should use GCLOUD_PROJECT in lieu of config.projectId', done => { +// process.env.GCLOUD_PROJECT = '11020304f2934-b'; +// const debug = new Debug({credentials: fakeCredentials}, packageInfo); + +// nocks.projectId('project-via-metadata'); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID}, +// }); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// assert.strictEqual( +// debuglet.debuggee!.project, +// process.env.GCLOUD_PROJECT +// ); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should use options.projectId in preference to the environment variable', done => { +// process.env.GCLOUD_PROJECT = 'should-not-be-used'; +// const debug = new Debug( +// {projectId: 'project-via-options', credentials: fakeCredentials}, +// packageInfo +// ); + +// nocks.projectId('project-via-metadata'); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID}, +// }); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// assert.strictEqual(debuglet.debuggee!.project, 'project-via-options'); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should respect GCLOUD_DEBUG_LOGLEVEL', done => { +// process.env.GCLOUD_PROJECT = '11020304f2934'; +// process.env.GCLOUD_DEBUG_LOGLEVEL = '3'; +// const debug = new Debug({credentials: fakeCredentials}, packageInfo); + +// nocks.projectId('project-via-metadata'); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID}, +// }); + +// let buffer = ''; +// const oldConsoleError = console.error; +// console.error = (str: string) => { +// buffer += str; +// }; + +// debuglet.once('registered', () => { +// const logger = debuglet.logger; +// const STRING1 = 'jjjjjjjjjjjjjjjjjfjfjfjf'; +// const STRING2 = 'kkkkkkkfkfkfkfkfkkffkkkk'; + +// logger.info(STRING1); +// logger.debug(STRING2); +// console.error = oldConsoleError; + +// assert(buffer.indexOf(STRING1) !== -1); +// assert(buffer.indexOf(STRING2) === -1); + +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should respect GAE_SERVICE and GAE_VERSION env. vars.', () => { +// process.env.GAE_SERVICE = 'fake-gae-service'; +// process.env.GAE_VERSION = 'fake-gae-version'; +// const debug = new Debug({}, packageInfo); +// const debuglet = new Debuglet(debug, defaultConfig); +// assert.ok(debuglet.config); +// assert.ok(debuglet.config.serviceContext); +// assert.strictEqual( +// debuglet.config.serviceContext.service, +// 'fake-gae-service' +// ); +// assert.strictEqual( +// debuglet.config.serviceContext.version, +// 'fake-gae-version' +// ); +// }); + +// it('should respect GAE_MODULE_NAME and GAE_MODULE_VERSION env. vars.', () => { +// process.env.GAE_MODULE_NAME = 'fake-gae-service'; +// process.env.GAE_MODULE_VERSION = 'fake-gae-version'; +// const debug = new Debug({}, packageInfo); +// const debuglet = new Debuglet(debug, defaultConfig); +// assert.ok(debuglet.config); +// assert.ok(debuglet.config.serviceContext); +// assert.strictEqual( +// debuglet.config.serviceContext.service, +// 'fake-gae-service' +// ); +// assert.strictEqual( +// debuglet.config.serviceContext.version, +// 'fake-gae-version' +// ); +// }); + +// it('should respect K_SERVICE and K_REVISION env. vars.', () => { +// process.env.K_SERVICE = 'fake-cloudrun-service'; +// process.env.K_REVISION = 'fake-cloudrun-version'; +// const debug = new Debug({}, packageInfo); +// const debuglet = new Debuglet(debug, defaultConfig); +// assert.ok(debuglet.config); +// assert.ok(debuglet.config.serviceContext); +// assert.strictEqual( +// debuglet.config.serviceContext.service, +// 'fake-cloudrun-service' +// ); +// assert.strictEqual( +// debuglet.config.serviceContext.version, +// 'fake-cloudrun-version' +// ); +// }); + +// it('should respect FUNCTION_NAME env. var.', () => { +// process.env.FUNCTION_NAME = 'fake-fn-name'; +// const debug = new Debug({}, packageInfo); +// const debuglet = new Debuglet(debug, defaultConfig); +// assert.ok(debuglet.config); +// assert.ok(debuglet.config.serviceContext); +// assert.strictEqual( +// debuglet.config.serviceContext.service, +// 'fake-fn-name' +// ); +// assert.strictEqual( +// debuglet.config.serviceContext.version, +// 'unversioned' +// ); +// }); + +// it('should prefer new flex vars over GAE_MODULE_*', () => { +// process.env.GAE_MODULE_NAME = 'fake-gae-module'; +// process.env.GAE_MODULE_VERSION = 'fake-gae-module-version'; +// process.env.GAE_SERVICE = 'fake-gae-service'; +// process.env.GAE_VERSION = 'fake-gae-version'; +// const debug = new Debug({}, packageInfo); +// const debuglet = new Debuglet(debug, defaultConfig); +// assert.ok(debuglet.config); +// assert.ok(debuglet.config.serviceContext); +// assert.strictEqual( +// debuglet.config.serviceContext.service, +// 'fake-gae-service' +// ); +// assert.strictEqual( +// debuglet.config.serviceContext.version, +// 'fake-gae-version' +// ); +// }); + +// it('should respect GAE_DEPLOYMENT_ID env. var. when available', () => { +// process.env.GAE_DEPLOYMENT_ID = 'some deployment id'; +// delete process.env.GAE_MINOR_VERSION; +// const debug = new Debug({}, packageInfo); +// const debuglet = new Debuglet(debug, defaultConfig); +// assert.ok(debuglet.config); +// assert.ok(debuglet.config.serviceContext); +// assert.strictEqual( +// debuglet.config.serviceContext.minorVersion_, +// 'some deployment id' +// ); +// }); + +// it('should respect GAE_MINOR_VERSION env. var. when available', () => { +// delete process.env.GAE_DEPLOYMENT_ID; +// process.env.GAE_MINOR_VERSION = 'some minor version'; +// const debug = new Debug({}, packageInfo); +// const debuglet = new Debuglet(debug, defaultConfig); +// assert.ok(debuglet.config); +// assert.ok(debuglet.config.serviceContext); +// assert.strictEqual( +// debuglet.config.serviceContext.minorVersion_, +// 'some minor version' +// ); +// }); + +// it('should prefer GAE_DEPLOYMENT_ID over GAE_MINOR_VERSION', () => { +// process.env.GAE_DEPLOYMENT_ID = 'some deployment id'; +// process.env.GAE_MINOR_VERSION = 'some minor version'; +// const debug = new Debug({}, packageInfo); +// const debuglet = new Debuglet(debug, defaultConfig); +// assert.ok(debuglet.config); +// assert.ok(debuglet.config.serviceContext); +// assert.strictEqual( +// debuglet.config.serviceContext.minorVersion_, +// 'some deployment id' +// ); +// }); + +// it('should not have minorVersion unless enviroment provides it', () => { +// const debug = new Debug({}, packageInfo); +// const debuglet = new Debuglet(debug, defaultConfig); +// assert.ok(debuglet.config); +// assert.ok(debuglet.config.serviceContext); +// // TODO: IMPORTANT: It appears that this test is incorrect as it +// // is. That is, if minorVersion is replaced with the +// // correctly named minorVersion_, then the test fails. +// // Resolve this. +// assert.strictEqual( +// undefined, +// ( +// debuglet.config.serviceContext as { +// minorVersion: {}; +// } +// ).minorVersion +// ); +// }); + +// it('should not provide minorversion upon registration on non flex', done => { +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH, (body: {debuggee: Debuggee}) => { +// assert.strictEqual(undefined, body.debuggee.labels!.minorversion); +// return true; +// }) +// .once() +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}); + +// // TODO: Determine if the id parameter should be used. +// debuglet.once('registered', () => { +// debuglet.stop(); +// scope.done(); +// done(); +// }); +// debuglet.start(); +// }); +// }); + +// it('should retry on failed registration', function (done) { +// this.timeout(10000); +// const debug = new Debug( +// {projectId: '11020304f2934', credentials: fakeCredentials}, +// packageInfo +// ); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(404) +// .post(REGISTER_PATH) +// .reply(509) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it("should error if a package.json doesn't exist", done => { +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); +// const config = extend({}, defaultConfig, { +// workingDirectory: __dirname, +// forceNewAgent_: true, +// }); +// const debuglet = new Debuglet(debug, config); + +// debuglet.once('initError', (err: Error) => { +// assert(err); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should by default error when workingDirectory is a root directory with a package.json', done => { +// const debug = new Debug({}, packageInfo); +// /* +// * `path.sep` represents a root directory on both Windows and Unix. +// * On Windows, `path.sep` resolves to the current drive. +// * +// * That is, after opening a command prompt in Windows, relative to the +// * drive C: and starting the Node REPL, the value of `path.sep` +// * represents `C:\\'. +// * +// * If `D:` is entered at the prompt to switch to the D: drive before +// * starting the Node REPL, `path.sep` represents `D:\\`. +// */ +// const root = path.sep; +// const mockedDebuglet = proxyquire('../src/agent/debuglet', { +// /* +// * Mock the 'fs' module to verify that if the root directory is used, +// * and the root directory is reported to contain a `package.json` +// * file, then the agent still produces an `initError` when the +// * working directory is the root directory. +// */ +// fs: { +// stat: ( +// filepath: string | Buffer, +// cb: (err: Error | null, stats: {}) => void +// ) => { +// if (filepath === path.join(root, 'package.json')) { +// // The key point is that looking for `package.json` in the +// // root directory does not cause an error. +// return cb(null, {}); +// } +// fs.stat(filepath, cb); +// }, +// }, +// }); +// const config = extend({}, defaultConfig, {workingDirectory: root}); +// const debuglet = new mockedDebuglet.Debuglet(debug, config); +// let text = ''; +// debuglet.logger.error = (str: string) => { +// text += str; +// }; + +// debuglet.on('initError', (err: Error) => { +// const errorMessage = +// 'The working directory is a root ' + +// 'directory. Disabling to avoid a scan of the entire filesystem ' + +// 'for JavaScript files. Use config `allowRootAsWorkingDirectory` ' + +// 'if you really want to do this.'; +// assert.ok(err); +// assert.strictEqual(err.message, errorMessage); +// assert.ok(text.includes(errorMessage)); +// done(); +// }); + +// debuglet.once('started', () => { +// assert.fail('Should not start if workingDirectory is a root directory'); +// }); + +// debuglet.start(); +// }); + +// it('should be able to force the workingDirectory to be a root directory', done => { +// const root = path.sep; +// // Act like the root directory contains a `package.json` file +// const mockedDebuglet = proxyquire('../src/agent/debuglet', { +// fs: { +// stat: ( +// filepath: string | Buffer, +// cb: (err: Error | null, stats: {}) => void +// ) => { +// if (filepath === path.join(root, 'package.json')) { +// return cb(null, {}); +// } +// fs.stat(filepath, cb); +// }, +// }, +// }); + +// // Don't actually scan the entire filesystem. Act like the filesystem +// // is empty. +// mockedDebuglet.Debuglet.findFiles = ( +// config: ResolvedDebugAgentConfig +// ): Promise => { +// const baseDir = config.workingDirectory; +// assert.strictEqual(baseDir, root); +// return Promise.resolve({ +// jsStats: {}, +// mapFiles: [], +// errors: new Map(), +// hash: 'fake-hash', +// }); +// }; + +// // No need to restore `findFiles` because we are modifying a +// // mocked version of `Debuglet` not `Debuglet` itself. + +// const config = extend({}, defaultConfig, { +// workingDirectory: root, +// allowRootAsWorkingDirectory: true, +// }); + +// const debug = new Debug({}, packageInfo); + +// // Act like the debuglet can get a project id +// debug.authClient.getProjectId = async () => 'some-project-id'; + +// const debuglet = new mockedDebuglet.Debuglet(debug, config); + +// debuglet.on('initError', (err: Error) => { +// assert.ifError(err); +// done(); +// }); + +// debuglet.once('started', () => { +// debuglet.stop(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should register successfully otherwise', done => { +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// nocks.oauth2(); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID}, +// }); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// it('should stop successfully even if stop is called quickly', () => { +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// nocks.oauth2(); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); + +// debuglet.start(); +// debuglet.stop(); +// }); + +// it('should register successfully even if stop was called first', done => { +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// nocks.oauth2(); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID}, +// }); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.stop(); + +// debuglet.start(); +// }); + +// it('should attempt to retrieve cluster name if needed', done => { +// const savedRunningOnGCP = Debuglet.runningOnGCP; +// Debuglet.runningOnGCP = () => { +// return Promise.resolve(true); +// }; +// const clusterScope = nock(gcpMetadata.HOST_ADDRESS) +// .get('/computeMetadata/v1/instance/attributes/cluster-name') +// .once() +// .reply(200, 'cluster-name-from-metadata'); + +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// nocks.oauth2(); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID}, +// }); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// debuglet.stop(); +// clusterScope.done(); +// scope.done(); +// Debuglet.runningOnGCP = savedRunningOnGCP; +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should attempt to retreive region correctly if needed', done => { +// const savedGetPlatform = Debuglet.getPlatform; +// Debuglet.getPlatform = () => Platforms.CLOUD_FUNCTION; + +// const clusterScope = nock(gcpMetadata.HOST_ADDRESS) +// .get('/computeMetadata/v1/instance/region') +// .once() +// .reply(200, '123/456/region_name', gcpMetadata.HEADERS); + +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// nocks.oauth2(); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID}, +// }); + +// debuglet.once('registered', () => { +// Debuglet.getPlatform = savedGetPlatform; +// assert.strictEqual( +// (debuglet.debuggee as Debuggee).labels?.region, +// 'region_name' +// ); +// debuglet.stop(); +// clusterScope.done(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should continue to register when could not get region', done => { +// const savedGetPlatform = Debuglet.getPlatform; +// Debuglet.getPlatform = () => Platforms.CLOUD_FUNCTION; + +// const clusterScope = nock(gcpMetadata.HOST_ADDRESS) +// .get('/computeMetadata/v1/instance/region') +// .once() +// .reply(400); + +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// nocks.oauth2(); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID}, +// }); + +// debuglet.once('registered', () => { +// Debuglet.getPlatform = savedGetPlatform; +// debuglet.stop(); +// clusterScope.done(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should pass config source context to api', done => { +// const REPO_URL = +// 'https://github.com/non-existent-users/non-existend-repo'; +// const REVISION_ID = '5'; + +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// const config = debugletConfig({ +// sourceContext: {url: REPO_URL, revisionId: REVISION_ID}, +// }); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH, (body: {debuggee: Debuggee}) => { +// const context = body.debuggee.sourceContexts![0]; +// return ( +// context && +// context.url === REPO_URL && +// context.revisionId === REVISION_ID +// ); +// }) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should pass source context to api if present', done => { +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); +// const old = Debuglet.getSourceContextFromFile; +// Debuglet.getSourceContextFromFile = async () => { +// return {a: 5 as {} as string}; +// }; + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH, (body: {debuggee: Debuggee}) => { +// const context = body.debuggee.sourceContexts![0]; +// return context && context.a === 5; +// }) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}); + +// debuglet.once('registered', (id: string) => { +// Debuglet.getSourceContextFromFile = old; +// assert.strictEqual(id, DEBUGGEE_ID); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should prefer config source context to file', done => { +// const REPO_URL = +// 'https://github.com/non-existent-users/non-existend-repo'; +// const REVISION_ID = '5'; + +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// const old = Debuglet.getSourceContextFromFile; +// Debuglet.getSourceContextFromFile = async () => { +// return {a: 5 as {} as string}; +// }; + +// const config = debugletConfig({ +// sourceContext: {url: REPO_URL, revisionId: REVISION_ID}, +// }); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH, (body: {debuggee: Debuggee}) => { +// const context = body.debuggee.sourceContexts![0]; +// return ( +// context && +// context.url === REPO_URL && +// context.revisionId === REVISION_ID +// ); +// }) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}); + +// debuglet.once('registered', (id: string) => { +// Debuglet.getSourceContextFromFile = old; +// assert.strictEqual(id, DEBUGGEE_ID); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should de-activate when the server responds with isDisabled', function (done) { +// this.timeout(4000); +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, { +// debuggee: {id: DEBUGGEE_ID, isDisabled: true}, +// }); + +// debuglet.once('remotelyDisabled', () => { +// assert.ok(!debuglet.fetcherActive); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should retry after a isDisabled request', function (done) { +// this.timeout(4000); +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID, isDisabled: true}}) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}); + +// let gotDisabled = false; +// debuglet.once('remotelyDisabled', () => { +// assert.ok(!debuglet.fetcherActive); +// gotDisabled = true; +// }); + +// debuglet.once('registered', (id: string) => { +// assert.ok(gotDisabled); +// assert.strictEqual(id, DEBUGGEE_ID); +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should re-register when registration expires', done => { +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(404) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}); + +// debuglet.once('registered', (id1: string) => { +// assert.strictEqual(id1, DEBUGGEE_ID); +// debuglet.once('registered', (id2: string) => { +// assert.strictEqual(id2, DEBUGGEE_ID); +// debuglet.stop(); +// scope.done(); +// done(); +// }); +// }); + +// debuglet.start(); +// }); + +// it('should fetch and add breakpoints', function (done) { +// this.timeout(2000); +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(200, {breakpoints: [bp]}); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// setTimeout(() => { +// assert.deepStrictEqual(debuglet.activeBreakpointMap.test, bp); +// debuglet.stop(); +// scope.done(); +// done(); +// }, 1000); +// }); + +// debuglet.start(); +// }); + +// it('should have breakpoints fetched when promise is resolved', function (done) { +// this.timeout(2000); +// const breakpoint: stackdriver.Breakpoint = { +// id: 'test1', +// action: 'CAPTURE', +// location: {path: 'build/test/fixtures/foo.js', line: 2}, +// } as stackdriver.Breakpoint; + +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(200, {breakpoints: [breakpoint]}); +// const debugPromise = debuglet.isReadyManager.isReady(); +// debuglet.once('registered', () => { +// debugPromise.then(() => { +// // Once debugPromise is resolved, debuggee must be registered. +// assert(debuglet.debuggee); +// setTimeout(() => { +// assert.deepStrictEqual( +// debuglet.activeBreakpointMap.test1, +// breakpoint +// ); +// debuglet.activeBreakpointMap = {}; +// debuglet.stop(); +// scope.done(); +// done(); +// }, 1000); +// }); +// }); +// debuglet.start(); +// }); + +// it('should resolve breakpointFetched promise when registration expires', function (done) { +// this.timeout(2000); +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// // const debuglet = new Debuglet(debug, defaultConfig); +// // const scope = nock(API) +// /// this change to a unique scope per test causes +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(404); // signal re-registration. +// const debugPromise = debuglet.isReadyManager.isReady(); +// debugPromise.then(() => { +// debuglet.stop(); +// scope.done(); +// done(); +// }); + +// debuglet.start(); +// }); + +// it('should reject breakpoints with conditions when allowExpressions=false', function (done) { +// this.timeout(2000); +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// const config = debugletConfig({allowExpressions: false}); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(200, { +// breakpoints: [ +// { +// id: 'test', +// action: 'CAPTURE', +// condition: 'x === 5', +// location: {path: 'fixtures/foo.js', line: 2}, +// }, +// ], +// }) +// .put( +// BPS_PATH + '/test', +// verifyBreakpointRejection.bind(null, EXPRESSIONS_REGEX) +// ) +// .reply(200); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// setTimeout(() => { +// assert.ok(!debuglet.activeBreakpointMap.test); +// debuglet.stop(); +// debuglet.config.allowExpressions = true; +// scope.done(); +// done(); +// }, 1000); +// }); + +// debuglet.start(); +// }); + +// it('should reject breakpoints with expressions when allowExpressions=false', function (done) { +// this.timeout(2000); +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// const config = debugletConfig({allowExpressions: false}); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(200, { +// breakpoints: [ +// { +// id: 'test', +// action: 'CAPTURE', +// expressions: ['x'], +// location: {path: 'fixtures/foo.js', line: 2}, +// }, +// ], +// }) +// .put( +// BPS_PATH + '/test', +// verifyBreakpointRejection.bind(null, EXPRESSIONS_REGEX) +// ) +// .reply(200); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// setTimeout(() => { +// assert.ok(!debuglet.activeBreakpointMap.test); +// debuglet.stop(); +// debuglet.config.allowExpressions = true; +// scope.done(); +// done(); +// }, 1000); +// }); + +// debuglet.start(); +// }); + +// it('should re-fetch breakpoints on error', function (done) { +// this.timeout(6000); + +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// const config = debugletConfig(); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(404) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(200, {waitExpired: true}) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(200, {breakpoints: [bp, errorBp]}) +// .put( +// BPS_PATH + '/' + errorBp.id, +// (body: {breakpoint: stackdriver.Breakpoint}) => { +// const status = body.breakpoint.status; +// return ( +// status!.isError && +// status!.description.format.indexOf('actions are CAPTURE') !== -1 +// ); +// } +// ) +// .reply(200); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// setTimeout(() => { +// assert.deepStrictEqual(debuglet.activeBreakpointMap.test, bp); +// assert(!debuglet.activeBreakpointMap.testLog); +// debuglet.stop(); +// scope.done(); +// done(); +// }, 1000); +// }); + +// debuglet.start(); +// }); + +// it('should expire stale breakpoints', function (done) { +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// this.timeout(6000); + +// const config = debugletConfig({ +// breakpointExpirationSec: 1, +// forceNewAgent_: true, +// }); +// const debuglet = new Debuglet(debug, config); +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(200, {breakpoints: [bp]}) +// .put( +// BPS_PATH + '/test', +// (body: {breakpoint: stackdriver.Breakpoint}) => { +// const status = body.breakpoint.status; +// return ( +// status!.description.format === 'The snapshot has expired' && +// status!.refersTo === 'BREAKPOINT_AGE' +// ); +// } +// ) +// .reply(200); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// setTimeout(() => { +// assert.deepStrictEqual(debuglet.activeBreakpointMap.test, bp); +// setTimeout(() => { +// assert(!debuglet.activeBreakpointMap.test); +// debuglet.stop(); +// scope.done(); +// done(); +// }, 1100); +// }, 500); +// }); + +// debuglet.start(); +// }); + +// // This test catches regressions in a bug where the agent would +// // re-schedule an already expired breakpoint to expire if the +// // server listed the breakpoint as active (which it may do depending +// // on how quickly the expiry is processed). +// // The test expires a breakpoint and then has the api respond with +// // the breakpoint listed as active. It validates that the breakpoint +// // is only expired with the server once. +// it('should not update expired breakpoints', function (done) { +// const debug = new Debug( +// {projectId: 'fake-project', credentials: fakeCredentials}, +// packageInfo +// ); + +// this.timeout(6000); + +// const config = debugletConfig({ +// breakpointExpirationSec: 1, +// breakpointUpdateIntervalSec: 1, +// forceNewAgent_: true, +// }); +// const debuglet = new Debuglet(debug, config); +// let unexpectedUpdate = false; +// const scope = nock(config.apiUrl) +// .post(REGISTER_PATH) +// .reply(200, {debuggee: {id: DEBUGGEE_ID}}) +// .get(BPS_PATH + '?successOnTimeout=true') +// .reply(200, {breakpoints: [bp]}) +// .put( +// BPS_PATH + '/test', +// (body: {breakpoint: stackdriver.Breakpoint}) => { +// return ( +// body.breakpoint.status!.description.format === +// 'The snapshot has expired' +// ); +// } +// ) +// .reply(200) +// .get(BPS_PATH + '?successOnTimeout=true') +// .times(4) +// .reply(200, {breakpoints: [bp]}); + +// // Get ready to fail the test if any additional updates come through. +// nock.emitter.on('no match', req => { +// if (req.path.startsWith(BPS_PATH) && req.method === 'PUT') { +// unexpectedUpdate = true; +// } +// }); + +// debuglet.once('registered', (id: string) => { +// assert.strictEqual(id, DEBUGGEE_ID); +// setTimeout(() => { +// assert.deepStrictEqual(debuglet.activeBreakpointMap.test, bp); +// setTimeout(() => { +// assert(!debuglet.activeBreakpointMap.test); +// assert(!unexpectedUpdate, 'unexpected update request'); +// debuglet.stop(); +// scope.done(); +// done(); +// }, 4500); +// }, 500); +// }); + +// debuglet.start(); +// }); +// }); + +// describe('map subtract', () => { +// it('should be correct', () => { +// const a = {a: 1, b: 2}; +// const b = {a: 1}; +// assert.deepStrictEqual(Debuglet.mapSubtract(a, b), [2]); +// assert.deepStrictEqual(Debuglet.mapSubtract(b, a), []); +// assert.deepStrictEqual(Debuglet.mapSubtract(a, {}), [1, 2]); +// assert.deepStrictEqual(Debuglet.mapSubtract({}, b), []); +// }); +// }); + +// describe('format', () => { +// it('should be correct', () => { +// // TODO: Determine if Debuglet.format() should allow a number[] +// // or if only string[] should be allowed. +// assert.deepStrictEqual( +// Debuglet.format('hi', [5] as {} as string[]), +// 'hi' +// ); +// assert.deepStrictEqual( +// Debuglet.format('hi $0', [5] as {} as string[]), +// 'hi 5' +// ); +// assert.deepStrictEqual( +// Debuglet.format('hi $0 $1', [5, 'there'] as {} as string[]), +// 'hi 5 there' +// ); +// assert.deepStrictEqual( +// Debuglet.format('hi $0 $1', [5] as {} as string[]), +// 'hi 5 $1' +// ); +// assert.deepStrictEqual( +// Debuglet.format('hi $0 $1 $0', [5] as {} as string[]), +// 'hi 5 $1 5' +// ); +// assert.deepStrictEqual( +// Debuglet.format('hi $$', [5] as {} as string[]), +// 'hi $' +// ); +// assert.deepStrictEqual( +// Debuglet.format('hi $$0', [5] as {} as string[]), +// 'hi $0' +// ); +// assert.deepStrictEqual( +// Debuglet.format('hi $00', [5] as {} as string[]), +// 'hi 50' +// ); +// assert.deepStrictEqual( +// Debuglet.format('hi $0', ['$1', 5] as {} as string[]), +// 'hi $1' +// ); +// assert.deepStrictEqual( +// Debuglet.format('hi $11', [ +// 0, +// 1, +// 2, +// 3, +// 4, +// 5, +// 6, +// 7, +// 8, +// 9, +// 'a', +// 'b', +// 'c', +// 'd', +// ] as {} as string[]), +// 'hi b' +// ); +// }); +// }); + +// describe('createDebuggee', () => { +// it('should have sensible labels', () => { +// const debuggee = Debuglet.createDebuggee( +// 'some project', +// 'id', +// {service: 'some-service', version: 'production'}, +// {}, +// false, +// packageInfo, +// Platforms.DEFAULT +// ); +// assert.ok(debuggee); +// assert.ok(debuggee.labels); +// assert.strictEqual(debuggee.labels!.module, 'some-service'); +// assert.strictEqual(debuggee.labels!.version, 'production'); +// }); + +// it('should not add a module label when service is default', () => { +// const debuggee = Debuglet.createDebuggee( +// 'fancy-project', +// 'very-unique', +// {service: 'default', version: 'yellow.5'}, +// {}, +// false, +// packageInfo, +// Platforms.DEFAULT +// ); +// assert.ok(debuggee); +// assert.ok(debuggee.labels); +// assert.strictEqual(debuggee.labels!.module, undefined); +// assert.strictEqual(debuggee.labels!.version, 'yellow.5'); +// }); + +// it('should have an error statusMessage with the appropriate arg', () => { +// const debuggee = Debuglet.createDebuggee( +// 'a', +// 'b', +// {}, +// {}, +// false, +// packageInfo, +// Platforms.DEFAULT, +// undefined, +// 'Some Error Message' +// ); +// assert.ok(debuggee); +// assert.ok(debuggee.statusMessage); +// }); + +// it('should be in CANARY_MODE_DEFAULT_ENABLED canaryMode', () => { +// const debuggee = Debuglet.createDebuggee( +// 'some project', +// 'id', +// {enableCanary: true, allowCanaryOverride: true}, +// {}, +// false, +// packageInfo, +// Platforms.DEFAULT +// ); +// assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_ENABLED'); +// }); + +// it('should be in CANARY_MODE_ALWAYS_ENABLED canaryMode', () => { +// const debuggee = Debuglet.createDebuggee( +// 'some project', +// 'id', +// {enableCanary: true, allowCanaryOverride: false}, +// {}, +// false, +// packageInfo, +// Platforms.DEFAULT +// ); +// assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_ENABLED'); +// }); + +// it('should be in CANARY_MODE_DEFAULT_DISABLED canaryMode', () => { +// const debuggee = Debuglet.createDebuggee( +// 'some project', +// 'id', +// {enableCanary: false, allowCanaryOverride: true}, +// {}, +// false, +// packageInfo, +// Platforms.DEFAULT +// ); +// assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_DISABLED'); +// }); + +// it('should be in CANARY_MODE_ALWAYS_DISABLED canaryMode', () => { +// const debuggee = Debuglet.createDebuggee( +// 'some project', +// 'id', +// {enableCanary: false, allowCanaryOverride: false}, +// {}, +// false, +// packageInfo, +// Platforms.DEFAULT +// ); +// assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_DISABLED'); +// }); +// }); + +// describe('getPlatform', () => { +// it('should correctly identify default platform.', () => { +// assert.ok(Debuglet.getPlatform() === Platforms.DEFAULT); +// }); + +// it('should correctly identify GCF (legacy) platform.', () => { +// // GCF sets this env var on older runtimes. +// process.env.FUNCTION_NAME = 'mock'; +// assert.ok(Debuglet.getPlatform() === Platforms.CLOUD_FUNCTION); +// delete process.env.FUNCTION_NAME; // Don't contaminate test environment. +// }); + +// it('should correctly identify GCF (modern) platform.', () => { +// // GCF sets this env var on modern runtimes. +// process.env.FUNCTION_TARGET = 'mock'; +// assert.ok(Debuglet.getPlatform() === Platforms.CLOUD_FUNCTION); +// delete process.env.FUNCTION_TARGET; // Don't contaminate test environment. +// }); +// }); + +// describe('getRegion', () => { +// it('should return function region for older GCF runtime', async () => { +// process.env.FUNCTION_REGION = 'mock'; + +// assert.ok((await Debuglet.getRegion()) === 'mock'); + +// delete process.env.FUNCTION_REGION; +// }); + +// it('should return region for newer GCF runtime', async () => { +// const clusterScope = nock(gcpMetadata.HOST_ADDRESS) +// .get('/computeMetadata/v1/instance/region') +// .once() +// .reply(200, '123/456/region_name', gcpMetadata.HEADERS); + +// assert.ok((await Debuglet.getRegion()) === 'region_name'); + +// clusterScope.done(); +// }); + +// it('should return undefined when cannot get region metadata', async () => { +// const clusterScope = nock(gcpMetadata.HOST_ADDRESS) +// .get('/computeMetadata/v1/instance/region') +// .once() +// .reply(400); + +// assert.ok((await Debuglet.getRegion()) === undefined); + +// clusterScope.done(); +// }); +// }); + +// describe('_createUniquifier', () => { +// it('should create a unique string', () => { +// const fn = Debuglet._createUniquifier; + +// const desc = 'description'; +// const version = 'version'; +// const uid = 'uid'; +// const sourceContext = {git: 'something'}; +// const labels = {key: 'value'}; + +// const u1 = fn(desc, version, uid, sourceContext, labels); + +// assert.strictEqual(fn(desc, version, uid, sourceContext, labels), u1); + +// assert.notStrictEqual( +// fn('foo', version, uid, sourceContext, labels), +// u1, +// 'changing the description should change the result' +// ); +// assert.notStrictEqual( +// fn(desc, '1.2', uid, sourceContext, labels), +// u1, +// 'changing the version should change the result' +// ); +// assert.notStrictEqual( +// fn(desc, version, '5', sourceContext, labels), +// u1, +// 'changing the description should change the result' +// ); +// assert.notStrictEqual( +// fn(desc, version, uid, {git: 'blah'}, labels), +// u1, +// 'changing the sourceContext should change the result' +// ); +// assert.notStrictEqual( +// fn(desc, version, uid, sourceContext, {key1: 'value2'}), +// u1, +// 'changing the labels should change the result' +// ); +// }); +// }); +// }); // a counter for unique test urls. -let apiUrlInc = 0; - -/** - * returns a new config object to be passed to debuglet. always has apiUrl - * @param conf custom config values - */ -function debugletConfig(conf?: {}): ResolvedDebugAgentConfig & { - apiUrl: string; -} { - const apiUrl = 'https://clouddebugger.googleapis.com' + ++apiUrlInc; - const c = Object.assign( - {}, - DEFAULT_CONFIG, - conf - ) as ResolvedDebugAgentConfig & {apiUrl: string}; - c.apiUrl = apiUrl; - return c; -} +// let apiUrlInc = 0; + +// /** +// * returns a new config object to be passed to debuglet. always has apiUrl +// * @param conf custom config values +// */ +// function debugletConfig(conf?: {}): ResolvedDebugAgentConfig & { +// apiUrl: string; +// } { +// const apiUrl = 'https://clouddebugger.googleapis.com' + ++apiUrlInc; +// const c = Object.assign( +// {}, +// DEFAULT_CONFIG, +// conf +// ) as ResolvedDebugAgentConfig & {apiUrl: string}; +// c.apiUrl = apiUrl; +// return c; +// } diff --git a/test/test-module.ts b/test/test-module.ts index fb8e84cf..c73538ab 100644 --- a/test/test-module.ts +++ b/test/test-module.ts @@ -25,45 +25,47 @@ import {Debuglet} from '../src/agent/debuglet'; nock.disableNetConnect(); -describe('Debug module', () => { - before(done => { - nocks.projectId('project-via-metadata'); - const debuglet = m.start({ - projectId: '0', - debug: {forceNewAgent_: true, testMode_: true}, - }); - debuglet.on('started', () => { - debuglet.stop(); - done(); - }); - }); +// TODO: Re-enable once there's mocking that allows firebase client to connect. - it('should throw on attempt to start a new agent', () => { - assert.throws(() => { - m.start(); - }); - }); +// describe('Debug module', () => { +// before(done => { +// nocks.projectId('project-via-metadata'); +// const debuglet = m.start({ +// projectId: '0', +// debug: {forceNewAgent_: true, testMode_: true}, +// }); +// debuglet.on('started', () => { +// debuglet.stop(); +// done(); +// }); +// }); - it('should return the agent via the get() method', () => { - const agent = m.get(); - assert(agent, 'Expected to get the started agent'); - assert.strictEqual(agent!.config.projectId, '0'); - }); -}); +// it('should throw on attempt to start a new agent', () => { +// assert.throws(() => { +// m.start(); +// }); +// }); -describe('Debug module without start() called', () => { - it('get() should return `undefined`', () => { - delete require.cache[require.resolve('../..')]; - const m: NodeModule & { - start: Function; - get: () => Debuglet | undefined; - // eslint-disable-next-line @typescript-eslint/no-var-requires - } = require('../..'); - const agent = m.get(); - assert.strictEqual( - agent, - undefined, - 'Expected `undefined` since the agent was not started' - ); - }); -}); +// it('should return the agent via the get() method', () => { +// const agent = m.get(); +// assert(agent, 'Expected to get the started agent'); +// assert.strictEqual(agent!.config.projectId, '0'); +// }); +// }); + +// describe('Debug module without start() called', () => { +// it('get() should return `undefined`', () => { +// delete require.cache[require.resolve('../..')]; +// const m: NodeModule & { +// start: Function; +// get: () => Debuglet | undefined; +// // eslint-disable-next-line @typescript-eslint/no-var-requires +// } = require('../..'); +// const agent = m.get(); +// assert.strictEqual( +// agent, +// undefined, +// 'Expected `undefined` since the agent was not started' +// ); +// }); +// }); diff --git a/test/test-options-credentials.ts b/test/test-options-credentials.ts index e2d75d23..23f481b5 100644 --- a/test/test-options-credentials.ts +++ b/test/test-options-credentials.ts @@ -21,7 +21,6 @@ import * as path from 'path'; import * as config from '../src/agent/config'; import {DebugAgentConfig} from '../src/agent/config'; import {Debuglet} from '../src/agent/debuglet'; -import {Debug} from '../src/client/stackdriver/debug'; import * as nocks from './nocks'; @@ -33,119 +32,121 @@ const packageInfo = { nock.disableNetConnect(); -describe('test-options-credentials', () => { - let debuglet: Debuglet | null = null; +// TODO: Write tests that verify the choice of credentials when using Firebase. - beforeEach(() => { - delete process.env.GCLOUD_PROJECT; - assert.strictEqual(debuglet, null); - }); +// describe('test-options-credentials', () => { +// let debuglet: Debuglet | null = null; - afterEach(() => { - assert.ok(debuglet); - debuglet!.stop(); - debuglet = null; - process.env.GCLOUD_PROJECT = envProject; - }); +// beforeEach(() => { +// delete process.env.GCLOUD_PROJECT; +// assert.strictEqual(debuglet, null); +// }); - it('should use the keyFilename field of the options object', done => { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const credentials = require('./fixtures/gcloud-credentials.json'); - const options = extend( - {}, - { - projectId: 'fake-project', - keyFilename: path.join( - __dirname, - 'fixtures', - 'gcloud-credentials.json' - ), - } - ); - const debug = new Debug(options, packageInfo); - const scope = nocks.oauth2(body => { - assert.strictEqual(body.client_id, credentials.client_id); - assert.strictEqual(body.client_secret, credentials.client_secret); - assert.strictEqual(body.refresh_token, credentials.refresh_token); - return true; - }); - // Since we have to get an auth token, this always gets intercepted second. - nocks.register(() => { - scope.done(); - setImmediate(done); - return true; - }); - nocks.projectId('project-via-metadata'); - // TODO: Determine how to remove this cast. - debuglet = new Debuglet(debug, config as {} as DebugAgentConfig); - debuglet.start(); - }); +// afterEach(() => { +// assert.ok(debuglet); +// debuglet!.stop(); +// debuglet = null; +// process.env.GCLOUD_PROJECT = envProject; +// }); - it('should use the credentials field of the options object', done => { - const options = extend( - {}, - { - projectId: 'fake-project', - credentials: require('./fixtures/gcloud-credentials.json'), - } - ); - const debug = new Debug(options, packageInfo); - const scope = nocks.oauth2(body => { - assert.strictEqual(body.client_id, options.credentials.client_id); - assert.strictEqual(body.client_secret, options.credentials.client_secret); - assert.strictEqual(body.refresh_token, options.credentials.refresh_token); - return true; - }); - // Since we have to get an auth token, this always gets intercepted second. - nocks.register(() => { - scope.done(); - setImmediate(done); - return true; - }); - nocks.projectId('project-via-metadata'); - // TODO: Determine how to remove this cast. - debuglet = new Debuglet(debug, config as {} as DebugAgentConfig); - debuglet.start(); - }); +// it('should use the keyFilename field of the options object', done => { +// // eslint-disable-next-line @typescript-eslint/no-var-requires +// const credentials = require('./fixtures/gcloud-credentials.json'); +// const options = extend( +// {}, +// { +// projectId: 'fake-project', +// keyFilename: path.join( +// __dirname, +// 'fixtures', +// 'gcloud-credentials.json' +// ), +// } +// ); +// const debug = new Debug(options, packageInfo); +// const scope = nocks.oauth2(body => { +// assert.strictEqual(body.client_id, credentials.client_id); +// assert.strictEqual(body.client_secret, credentials.client_secret); +// assert.strictEqual(body.refresh_token, credentials.refresh_token); +// return true; +// }); +// // Since we have to get an auth token, this always gets intercepted second. +// nocks.register(() => { +// scope.done(); +// setImmediate(done); +// return true; +// }); +// nocks.projectId('project-via-metadata'); +// // TODO: Determine how to remove this cast. +// debuglet = new Debuglet(debug, config as {} as DebugAgentConfig); +// debuglet.start(); +// }); - it('should ignore keyFilename if credentials is provided', done => { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const fileCredentials = require('./fixtures/gcloud-credentials.json'); - const credentials: {[key: string]: string | undefined} = { - client_id: 'a', - client_secret: 'b', - refresh_token: 'c', - type: 'authorized_user', - }; - const options = extend( - {}, - { - projectId: 'fake-project', - keyFilename: path.join('test', 'fixtures', 'gcloud-credentials.json'), - credentials, - } - ); - const debug = new Debug(options, packageInfo); - const scope = nocks.oauth2(body => { - assert.strictEqual(body.client_id, credentials.client_id); - assert.strictEqual(body.client_secret, credentials.client_secret); - assert.strictEqual(body.refresh_token, credentials.refresh_token); - return true; - }); - // Since we have to get an auth token, this always gets intercepted second. - nocks.register(() => { - scope.done(); - setImmediate(done); - return true; - }); - nocks.projectId('project-via-metadata'); - ['client_id', 'client_secret', 'refresh_token'].forEach(field => { - assert(Object.prototype.hasOwnProperty.call(fileCredentials, field)); - assert(Object.prototype.hasOwnProperty.call(options.credentials, field)); - assert.notStrictEqual(options.credentials[field], fileCredentials[field]); - }); - // TODO: Determine how to remove this cast. - debuglet = new Debuglet(debug, config as {} as DebugAgentConfig); - debuglet.start(); - }); -}); +// it('should use the credentials field of the options object', done => { +// const options = extend( +// {}, +// { +// projectId: 'fake-project', +// credentials: require('./fixtures/gcloud-credentials.json'), +// } +// ); +// const debug = new Debug(options, packageInfo); +// const scope = nocks.oauth2(body => { +// assert.strictEqual(body.client_id, options.credentials.client_id); +// assert.strictEqual(body.client_secret, options.credentials.client_secret); +// assert.strictEqual(body.refresh_token, options.credentials.refresh_token); +// return true; +// }); +// // Since we have to get an auth token, this always gets intercepted second. +// nocks.register(() => { +// scope.done(); +// setImmediate(done); +// return true; +// }); +// nocks.projectId('project-via-metadata'); +// // TODO: Determine how to remove this cast. +// debuglet = new Debuglet(debug, config as {} as DebugAgentConfig); +// debuglet.start(); +// }); + +// it('should ignore keyFilename if credentials is provided', done => { +// // eslint-disable-next-line @typescript-eslint/no-var-requires +// const fileCredentials = require('./fixtures/gcloud-credentials.json'); +// const credentials: {[key: string]: string | undefined} = { +// client_id: 'a', +// client_secret: 'b', +// refresh_token: 'c', +// type: 'authorized_user', +// }; +// const options = extend( +// {}, +// { +// projectId: 'fake-project', +// keyFilename: path.join('test', 'fixtures', 'gcloud-credentials.json'), +// credentials, +// } +// ); +// const debug = new Debug(options, packageInfo); +// const scope = nocks.oauth2(body => { +// assert.strictEqual(body.client_id, credentials.client_id); +// assert.strictEqual(body.client_secret, credentials.client_secret); +// assert.strictEqual(body.refresh_token, credentials.refresh_token); +// return true; +// }); +// // Since we have to get an auth token, this always gets intercepted second. +// nocks.register(() => { +// scope.done(); +// setImmediate(done); +// return true; +// }); +// nocks.projectId('project-via-metadata'); +// ['client_id', 'client_secret', 'refresh_token'].forEach(field => { +// assert(Object.prototype.hasOwnProperty.call(fileCredentials, field)); +// assert(Object.prototype.hasOwnProperty.call(options.credentials, field)); +// assert.notStrictEqual(options.credentials[field], fileCredentials[field]); +// }); +// // TODO: Determine how to remove this cast. +// debuglet = new Debuglet(debug, config as {} as DebugAgentConfig); +// debuglet.start(); +// }); +// }); From 95c9078964852925e28f0f5e732ea2c9f407de88 Mon Sep 17 00:00:00 2001 From: James McTavish Date: Tue, 6 Jun 2023 13:04:26 -0400 Subject: [PATCH 09/10] docs: remove content about the shut down Cloud Debugger service (#1149) --- .readme-partials.yaml | 70 ++++++++++++++----------------------------- README.md | 70 ++++++++++++++----------------------------- samples/README.md | 6 ++-- 3 files changed, 50 insertions(+), 96 deletions(-) diff --git a/.readme-partials.yaml b/.readme-partials.yaml index cb4219f3..aba0bae6 100644 --- a/.readme-partials.yaml +++ b/.readme-partials.yaml @@ -1,8 +1,10 @@ introduction: |- - > This module provides Cloud Debugger support for Node.js applications. - Cloud Debugger is a feature of Google Cloud Platform that lets you debug your + > This module provides Snapshot Debugger support for Node.js applications. + Snapshot Debugger is an open source product that lets you debug your applications in production without stopping or pausing your application. + A Firebase Realtime Database instance is used to store your data. + body: |- ## Debugger Agent Settings @@ -24,56 +26,31 @@ body: |- }); ``` + The following options configure the connection to the Firebase database: + * firebaseDbUrl - https://**PROJECT_ID**-cdbg.firebaseio.com will be used if + not provided. where **PROJECT_ID** is your project ID. + * firebaseKeyPath - Default google application credentials are used if not + provided. + See [the agent configuration][config-ts] for a list of possible configuration options. ## Using the Debugger - Once your application is running, use the [Debug UI][debug-tab] in your Cloud - [developer console][dev-console] to debug your application. The Debug UI can - be found in the 'Operations -> Debug' section in the navigation panel, or by - simply searching for 'Debug' in the cloud console. - - To take a snapshot with the debugger: - 1. Click in the gutter (line number area) or enter a filename and line number - in the snapshot panel - 2. The debugger inserts a momentary breakpoint at the specified location in - your code in the running instance of your application. - 3. As soon as that line of code is reached in any of the running instances of - your application, the stack traces, local variables, and watch expressions - are captured, and your application continues. - - **Note:** The directory layout of the code that is being debugged does not - have to exactly match the source code specified in the Debug UI. This is - because the debug agent resolves a snapshot filename by searching for a file - with the longest matching path suffix. If a unique match is found, that file - will be used to set the snapshot. - - ## Snapshot Debugger - Firebase Realtime Database Backend - - This functionality is available for release 6.0.0 onward of this agent and - provides support for the Snapshot Debugger, which is being provided as a - replacement for the deprecated Cloud Debugger service. - - To enable the agent, add the following at the top of your app's main script - or entry point: - - ```js - require('@google-cloud/debug-agent').start({ - useFirebase: true, - firebaseDbUrl: 'https://my-database-url.firebaseio.com', - firebaseKeyPath: 'path/to/service_account.json', - }); - ``` + Once your application is running, use the + [Snapshot Debugger CLI](https://pypi.org/project/snapshot-dbg-cli/) or the + [VSCode extension][extension-page] + to debug your application. - The following params are optional: - * firebaseDbUrl - https://**PROJECT_ID**-cdbg.firebaseio.com will be used if - not provided. where **PROJECT_ID** is your project ID. - * firebaseKeyPath - Default google application credentials are used if not - provided. + ## Historical note - See https://github.com/GoogleCloudPlatform/snapshot-debugger and - https://cloud.google.com/debugger/docs/deprecations for more details. + Version 6.x and 7.x of this agent supported both the now shutdown Cloud + Debugger service (by default) and the + [Snapshot Debugger](https://github.com/GoogleCloudPlatform/snapshot-debugger/) + (Firebase RTDB backend) by setting the `useFirebase` flag to true. Version 8.0.0 + removed support for the Cloud Debugger service, making the Snapshot Debugger the + default. To note the `useFirebase` flag is now obsolete, but still present for + backward compatibility. ## Limitations and Requirements @@ -94,6 +71,5 @@ body: |- file. [config-ts]: https://github.com/googleapis/cloud-debug-nodejs/blob/master/src/agent/config.ts - [debug-tab]: https://console.cloud.google.com/debug - [dev-console]: https://console.cloud.google.com/ + [extension-page]: https://github.com/GoogleCloudPlatform/snapshot-debugger/tree/main/snapshot_dbg_extension [snapshot-debugger-readme]: https://github.com/GoogleCloudPlatform/snapshot-debugger#readme diff --git a/README.md b/README.md index e7809add..9983ea4f 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,12 @@ -> This module provides Cloud Debugger support for Node.js applications. -Cloud Debugger is a feature of Google Cloud Platform that lets you debug your +> This module provides Snapshot Debugger support for Node.js applications. +Snapshot Debugger is an open source product that lets you debug your applications in production without stopping or pausing your application. +A Firebase Realtime Database instance is used to store your data. + A comprehensive list of changes in each version may be found in [the CHANGELOG](https://github.com/googleapis/cloud-debug-nodejs/blob/main/CHANGELOG.md). @@ -74,56 +76,31 @@ require('@google-cloud/debug-agent').start({ }); ``` +The following options configure the connection to the Firebase database: + * firebaseDbUrl - https://**PROJECT_ID**-cdbg.firebaseio.com will be used if + not provided. where **PROJECT_ID** is your project ID. + * firebaseKeyPath - Default google application credentials are used if not + provided. + See [the agent configuration][config-ts] for a list of possible configuration options. ## Using the Debugger -Once your application is running, use the [Debug UI][debug-tab] in your Cloud -[developer console][dev-console] to debug your application. The Debug UI can -be found in the 'Operations -> Debug' section in the navigation panel, or by -simply searching for 'Debug' in the cloud console. - -To take a snapshot with the debugger: -1. Click in the gutter (line number area) or enter a filename and line number - in the snapshot panel -2. The debugger inserts a momentary breakpoint at the specified location in - your code in the running instance of your application. -3. As soon as that line of code is reached in any of the running instances of - your application, the stack traces, local variables, and watch expressions - are captured, and your application continues. - -**Note:** The directory layout of the code that is being debugged does not -have to exactly match the source code specified in the Debug UI. This is -because the debug agent resolves a snapshot filename by searching for a file -with the longest matching path suffix. If a unique match is found, that file -will be used to set the snapshot. - -## Snapshot Debugger - Firebase Realtime Database Backend - -This functionality is available for release 6.0.0 onward of this agent and -provides support for the Snapshot Debugger, which is being provided as a -replacement for the deprecated Cloud Debugger service. - -To enable the agent, add the following at the top of your app's main script -or entry point: - -```js -require('@google-cloud/debug-agent').start({ - useFirebase: true, - firebaseDbUrl: 'https://my-database-url.firebaseio.com', - firebaseKeyPath: 'path/to/service_account.json', -}); -``` +Once your application is running, use the +[Snapshot Debugger CLI](https://pypi.org/project/snapshot-dbg-cli/) or the +[VSCode extension][extension-page] +to debug your application. -The following params are optional: -* firebaseDbUrl - https://**PROJECT_ID**-cdbg.firebaseio.com will be used if - not provided. where **PROJECT_ID** is your project ID. -* firebaseKeyPath - Default google application credentials are used if not - provided. +## Historical note -See https://github.com/GoogleCloudPlatform/snapshot-debugger and -https://cloud.google.com/debugger/docs/deprecations for more details. +Version 6.x and 7.x of this agent supported both the now shutdown Cloud +Debugger service (by default) and the +[Snapshot Debugger](https://github.com/GoogleCloudPlatform/snapshot-debugger/) +(Firebase RTDB backend) by setting the `useFirebase` flag to true. Version 8.0.0 +removed support for the Cloud Debugger service, making the Snapshot Debugger the +default. To note the `useFirebase` flag is now obsolete, but still present for +backward compatibility. ## Limitations and Requirements @@ -144,8 +121,7 @@ leaks. See [#811](https://github.com/googleapis/cloud-debug-nodejs/issues/811) file. [config-ts]: https://github.com/googleapis/cloud-debug-nodejs/blob/master/src/agent/config.ts -[debug-tab]: https://console.cloud.google.com/debug -[dev-console]: https://console.cloud.google.com/ +[extension-page]: https://github.com/GoogleCloudPlatform/snapshot-debugger/tree/main/snapshot_dbg_extension [snapshot-debugger-readme]: https://github.com/GoogleCloudPlatform/snapshot-debugger#readme diff --git a/samples/README.md b/samples/README.md index 374b85f6..e6854afa 100644 --- a/samples/README.md +++ b/samples/README.md @@ -6,10 +6,12 @@ [![Open in Cloud Shell][shell_img]][shell_link] -> This module provides Cloud Debugger support for Node.js applications. -Cloud Debugger is a feature of Google Cloud Platform that lets you debug your +> This module provides Snapshot Debugger support for Node.js applications. +Snapshot Debugger is an open source product that lets you debug your applications in production without stopping or pausing your application. +A Firebase Realtime Database instance is used to store your data. + ## Table of Contents * [Before you begin](#before-you-begin) From 83183e26315c3bf4cab1024794c1100272568f6f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 19:18:12 +0000 Subject: [PATCH 10/10] chore(main): release 8.0.0 (#1146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :robot: I have created a release *beep* *boop* --- ## [8.0.0](https://togithub.com/googleapis/cloud-debug-nodejs/compare/v7.2.2...v8.0.0) (2023-06-06) ### âš  BREAKING CHANGES * Remove support for deprecated Cloud Debugger API. ([#1144](https://togithub.com/googleapis/cloud-debug-nodejs/issues/1144)) ### Features * Add stop function to allow shutting down the debug agent ([#1147](https://togithub.com/googleapis/cloud-debug-nodejs/issues/1147)) ([c292200](https://togithub.com/googleapis/cloud-debug-nodejs/commit/c292200ff83660d7f9c509af2c45c5e6a1565011)) * Remove support for deprecated Cloud Debugger API. ([#1144](https://togithub.com/googleapis/cloud-debug-nodejs/issues/1144)) ([7ed8fb2](https://togithub.com/googleapis/cloud-debug-nodejs/commit/7ed8fb2a16bea36903fbc72ebc066b19bb10143c)) ### Bug Fixes * Unblock compodoc page generation ([#1145](https://togithub.com/googleapis/cloud-debug-nodejs/issues/1145)) ([c5bbb4e](https://togithub.com/googleapis/cloud-debug-nodejs/commit/c5bbb4ead353614a548e1f962c9fdf3cc6ea9845)) --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- samples/package.json | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 501eca38..494d0560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Node.js Agent for Google Cloud Debug ChangeLog +## [8.0.0](https://github.com/googleapis/cloud-debug-nodejs/compare/v7.2.2...v8.0.0) (2023-06-06) + + +### âš  BREAKING CHANGES + +* Remove support for deprecated Cloud Debugger API. ([#1144](https://github.com/googleapis/cloud-debug-nodejs/issues/1144)) + +### Features + +* Add stop function to allow shutting down the debug agent ([#1147](https://github.com/googleapis/cloud-debug-nodejs/issues/1147)) ([c292200](https://github.com/googleapis/cloud-debug-nodejs/commit/c292200ff83660d7f9c509af2c45c5e6a1565011)) +* Remove support for deprecated Cloud Debugger API. ([#1144](https://github.com/googleapis/cloud-debug-nodejs/issues/1144)) ([7ed8fb2](https://github.com/googleapis/cloud-debug-nodejs/commit/7ed8fb2a16bea36903fbc72ebc066b19bb10143c)) + + +### Bug Fixes + +* Unblock compodoc page generation ([#1145](https://github.com/googleapis/cloud-debug-nodejs/issues/1145)) ([c5bbb4e](https://github.com/googleapis/cloud-debug-nodejs/commit/c5bbb4ead353614a548e1f962c9fdf3cc6ea9845)) + ## [7.2.2](https://github.com/googleapis/cloud-debug-nodejs/compare/v7.2.1...v7.2.2) (2023-01-16) diff --git a/package.json b/package.json index cedb7125..e390f99c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@google-cloud/debug-agent", - "version": "7.2.2", + "version": "8.0.0", "author": "Google Inc.", "description": "Stackdriver Debug Agent for Node.js", "main": "./build/src/index", diff --git a/samples/package.json b/samples/package.json index a279069e..cc039640 100644 --- a/samples/package.json +++ b/samples/package.json @@ -17,7 +17,7 @@ "test": "mocha" }, "dependencies": { - "@google-cloud/debug-agent": "^7.2.2", + "@google-cloud/debug-agent": "^8.0.0", "express": "4.18.2" }, "devDependencies": {