From 65ba82f0cc0086e2b142b456e6d50ccea8528f13 Mon Sep 17 00:00:00 2001 From: James McTavish Date: Thu, 7 Sep 2023 13:52:46 -0400 Subject: [PATCH] chore: Clean up dead code and linter errors (#1166) --- src/agent/firebase-controller.ts | 9 +- src/agent/state/legacy-state.ts | 628 ----------- test/debugger.ts | 87 -- test/test-debuglet.ts | 1781 +----------------------------- test/test-module.ts | 71 -- test/test-options-credentials.ts | 152 --- test/test-state.ts | 53 - 7 files changed, 9 insertions(+), 2772 deletions(-) delete mode 100644 src/agent/state/legacy-state.ts delete mode 100644 test/debugger.ts delete mode 100644 test/test-module.ts delete mode 100644 test/test-options-credentials.ts delete mode 100644 test/test-state.ts diff --git a/src/agent/firebase-controller.ts b/src/agent/firebase-controller.ts index a03d49fd..1a99255d 100644 --- a/src/agent/firebase-controller.ts +++ b/src/agent/firebase-controller.ts @@ -25,6 +25,7 @@ import * as crypto from 'crypto'; import * as firebase from 'firebase-admin'; import * as gcpMetadata from 'gcp-metadata'; +import {DataSnapshot} from 'firebase-admin/database'; import * as util from 'util'; const debuglog = util.debuglog('cdbg.firebase'); @@ -37,7 +38,11 @@ const FIREBASE_APP_NAME = 'cdbg'; * @param promise * @returns Promise wrapped in a timeout. */ -const withTimeout = (ms: number, promise: Promise) => { +const withTimeout = ( + ms: number, + promise: Promise +): Promise => { + // Note that the type above is constrained to make the linter happy. const timeout = new Promise((_, reject) => setTimeout(() => reject(`Timed out after ${ms} ms.`), ms) ); @@ -126,7 +131,7 @@ export class FirebaseController implements Controller { db.ref('cdbg/schema_version').get() ); if (version_snapshot) { - const version = version_snapshot.val(); + const version = (version_snapshot as DataSnapshot).val(); debuglog( `Firebase app initialized. Connected to ${databaseUrl}` + ` with schema version ${version}` diff --git a/src/agent/state/legacy-state.ts b/src/agent/state/legacy-state.ts deleted file mode 100644 index 2a5994e5..00000000 --- a/src/agent/state/legacy-state.ts +++ /dev/null @@ -1,628 +0,0 @@ -// Copyright 2014 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 util from 'util'; -import * as vm from 'vm'; -import * as acorn from 'acorn'; - -import {StatusMessage} from '../../client/stackdriver/status-message'; -import * as stackdriver from '../../types/stackdriver'; -import * as v8 from '../../types/v8'; -import {ResolvedDebugAgentConfig} from '../config'; -import {debugAssert} from '../util/debug-assert'; - -const assert = debugAssert(!!process.env.CLOUD_DEBUG_ASSERTIONS); - -// Error message indices into the resolved variable table. -const BUFFER_FULL_MESSAGE_INDEX = 0; -const NATIVE_PROPERTY_MESSAGE_INDEX = 1; -const GETTER_MESSAGE_INDEX = 2; -const ARG_LOCAL_LIMIT_MESSAGE_INDEX = 3; - -/** - * Checks that the provided expressions will not have side effects and - * then evaluates the expression in the current execution context. - * - * @return an object with error and mirror fields. - */ -export function evaluate( - expression: string, - frame: v8.FrameMirror -): {error: string | null; mirror?: v8.ValueMirror} { - // First validate the expression to make sure it doesn't mutate state. - // Using ecmaVersion 6 for consistency with legacy-debugapi. - try { - const ast = acorn.parse(expression, {sourceType: 'script', ecmaVersion: 6}); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const validator = require('../util/validator'); - if (!validator.isValid(ast)) { - return {error: 'Expression not allowed'}; - } - } catch (err) { - return {error: (err as Error).message}; - } - - // Now actually ask V8 to evaluate the expression - try { - const mirror = frame.evaluate(expression); - return {error: null, mirror}; - } catch (error) { - return {error: error as string}; - } -} - -interface ScopeType { - Global: {}; - Script: {}; - Closure: {}; - Local: {}; -} - -interface LegacyVm { - runInDebugContext: (context: string) => ScopeType; -} - -class StateResolver { - private state: v8.ExecutionState; - private expressions: string[]; - private config: ResolvedDebugAgentConfig; - private ctx: v8.Debug; - private evaluatedExpressions: stackdriver.Variable[]; - private totalSize: number; - private messageTable: stackdriver.Variable[]; - private resolvedVariableTable: stackdriver.Variable[]; - private rawVariableTable: Array; - private scopeType: ScopeType; - /** - * @param {!Object} execState - * @param {Array} expressions - * @param {!Object} config - * @constructor - */ - constructor( - execState: v8.ExecutionState, - expressions: string[], - config: ResolvedDebugAgentConfig, - v8debug: v8.Debug - ) { - this.state = execState; - this.expressions = expressions; - this.config = config; - this.ctx = v8debug; - - this.evaluatedExpressions = []; - this.totalSize = 0; - - this.messageTable = []; - this.messageTable[BUFFER_FULL_MESSAGE_INDEX] = { - status: new StatusMessage( - StatusMessage.VARIABLE_VALUE, - 'Max data size reached', - true - ), - }; - this.messageTable[NATIVE_PROPERTY_MESSAGE_INDEX] = { - status: new StatusMessage( - StatusMessage.VARIABLE_VALUE, - 'Native properties are not available', - true - ), - }; - this.messageTable[GETTER_MESSAGE_INDEX] = { - status: new StatusMessage( - StatusMessage.VARIABLE_VALUE, - 'Properties with getters are not available', - true - ), - }; - this.messageTable[ARG_LOCAL_LIMIT_MESSAGE_INDEX] = { - status: new StatusMessage( - StatusMessage.VARIABLE_VALUE, - 'Locals and arguments are only displayed for the ' + - 'top `config.capture.maxExpandFrames=' + - config.capture.maxExpandFrames + - '` stack frames.', - true - ), - }; - - // TODO: Determine why _extend is used here - this.resolvedVariableTable = (util as {} as {_extend: Function})._extend( - [], - this.messageTable - ); - this.rawVariableTable = this.messageTable.map(() => { - return null; - }); - - // This constructor is only used in situations where the legacy vm - // interface is used that has the `runInDebugContext` method. - this.scopeType = (vm as {} as LegacyVm).runInDebugContext('ScopeType'); - } - - /** - * Captures the stack and current execution state. - * - * @return an object with stackFrames, variableTable, and - * evaluatedExpressions fields - */ - capture_(): stackdriver.Breakpoint { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const that = this; - - // Evaluate the watch expressions - const evalIndexSet = new Set(); - if (that.expressions) { - that.expressions.forEach((expression, index2) => { - const result = evaluate(expression, that.state.frame(0)); - let evaluated; - - if (result.error) { - evaluated = { - name: expression, - status: new StatusMessage( - StatusMessage.VARIABLE_VALUE, - result.error, - true - ), - }; - } else { - // TODO: Determine how to not downcast this to v8.ValueMirror - // TODO: Handle the case where `result.mirror` is `undefined`. - evaluated = that.resolveVariable_( - expression, - result.mirror as v8.ValueMirror, - true - ); - const varTableIdx = evaluated.varTableIndex; - if (typeof varTableIdx !== 'undefined') { - evalIndexSet.add(varTableIdx); - } - } - that.evaluatedExpressions[index2] = evaluated; - }); - } - - // The frames are resolved after the evaluated expressions so that - // evaluated expressions can be evaluated as much as possible within - // the max data size limits - const frames = that.resolveFrames_(); - // Now resolve the variables - let index = this.messageTable.length; // skip the sentinel values - const noLimit = that.config.capture.maxDataSize === 0; - while ( - index < that.rawVariableTable.length && // NOTE: length changes in loop - (that.totalSize < that.config.capture.maxDataSize || noLimit) - ) { - assert.ok(!that.resolvedVariableTable[index]); // shouldn't have it - // resolved yet - const isEvaluated = evalIndexSet.has(index); - // TODO: This code suggests that an ObjectMirror and Stutus are the - // same. Resolve this. - that.resolvedVariableTable[index] = that.resolveMirror_( - that.rawVariableTable[index] as v8.ObjectMirror, - isEvaluated - ); - index++; - } - - // If we filled up the buffer already, we need to trim the remainder - if (index < that.rawVariableTable.length) { - that.trimVariableTable_(index, frames); - } - return { - // TODO (fgao): Add path attribute to avoid explicit cast to - // stackdriver.SourceLocation once breakpoint is passed in this class. - id: 'dummy-id', - location: { - line: this.state.frame(0).sourceLine() + 1, - } as stackdriver.SourceLocation, - stackFrames: frames, - variableTable: that.resolvedVariableTable, - evaluatedExpressions: that.evaluatedExpressions, - }; - } - - /** - * Limits the size of the variable table to `fromIndex` elements. It marks - * all variables with entries beyond `fromIndex` with a message indicating - * that the table filled. - * - * @param {Number} fromIndex The desired size of the variable table. - * @param {Object} frames Frames associated with the current execution - * environment. - */ - trimVariableTable_( - fromIndex: number, - frames: stackdriver.StackFrame[] - ): void { - this.resolvedVariableTable.splice(fromIndex); // remove the remaining entries - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const that = this; - const processBufferFull = (variables: stackdriver.Variable[]) => { - variables.forEach(variable => { - if (variable.varTableIndex && variable.varTableIndex >= fromIndex) { - // make it point to the sentinel 'buffer full' value - variable.varTableIndex = BUFFER_FULL_MESSAGE_INDEX; - variable.status = that.messageTable[BUFFER_FULL_MESSAGE_INDEX].status; - } - if (variable.members) { - processBufferFull(variable.members); - } - }); - }; - - frames.forEach(frame => { - processBufferFull(frame.arguments); - processBufferFull(frame.locals); - }); - processBufferFull(this.evaluatedExpressions); - processBufferFull(this.resolvedVariableTable); - } - - resolveFrames_(): stackdriver.StackFrame[] { - const frames: stackdriver.StackFrame[] = []; - const frameCount = Math.min( - this.state.frameCount(), - this.config.capture.maxFrames - ); - - for (let i = 0; i < frameCount; i++) { - const frame = this.state.frame(i); - if (this.shouldFrameBeResolved_(frame)) { - frames.push( - this.resolveFrame_(frame, i < this.config.capture.maxExpandFrames) - ); - } - } - return frames; - } - - shouldFrameBeResolved_(frame: v8.FrameMirror): boolean { - // Only capture data from the frames for which we can link the data back - // to the source files. - - const fullPath = this.resolveFullPath_(frame); - - if (!this.isPathInCurrentWorkingDirectory_(fullPath)) { - return false; - } - - const relativePath = this.resolveRelativePath_(frame); - if ( - !this.config.capture.includeNodeModules && - this.isPathInNodeModulesDirectory_(relativePath) - ) { - return false; - } - - return true; - } - - resolveFullPath_(frame: v8.FrameMirror): string { - const func = frame.func(); - if (!func.resolved()) { - return ''; - } - - const script = func.script(); - if (!script) { - return ''; - } - - return script.name(); - } - - resolveRelativePath_(frame: v8.FrameMirror): string { - const fullPath = this.resolveFullPath_(frame); - return this.stripCurrentWorkingDirectory_(fullPath); - } - - stripCurrentWorkingDirectory_(path: string): string { - // Strip 1 extra character to remove the slash. - return path.substr(this.config.workingDirectory.length + 1); - } - - isPathInCurrentWorkingDirectory_(path: string): boolean { - // return true; - return path.indexOf(this.config.workingDirectory) === 0; - } - - isPathInNodeModulesDirectory_(path: string): boolean { - return path.indexOf('node_modules') === 0; - } - - resolveFrame_( - frame: v8.FrameMirror, - underFrameCap: boolean - ): stackdriver.StackFrame { - const args: stackdriver.Variable[] = []; - // TODO: `locals` should be of type v8.ScopeMirror[] - // Resolve conflicts so that it can be specified of that type. - let locals: Array<{}> = []; - // Locals and arguments are safe to collect even when - // `config.allowExpressions=false` since we properly avoid inspecting - // interceptors and getters by default. - if (!underFrameCap) { - args.push({ - name: 'arguments_not_available', - varTableIndex: ARG_LOCAL_LIMIT_MESSAGE_INDEX, - }); - locals.push({ - name: 'locals_not_available', - varTableIndex: ARG_LOCAL_LIMIT_MESSAGE_INDEX, - }); - } else { - // We will use the values aggregated from the ScopeMirror traversal stored - // in locals which will include any applicable arguments from the - // invocation. - locals = this.resolveLocalsList_(frame); - if (Array.isArray(locals) && locals.length === 0) { - locals = []; - } - } - return { - function: this.resolveFunctionName_(frame.func()), - location: this.resolveLocation_(frame), - arguments: args, - locals, - }; - } - - resolveFunctionName_(func: v8.FunctionMirror): string { - if (!func || !func.isFunction()) { - return ''; - } - return func.name() || func.inferredName() || '(anonymous function)'; - } - - resolveLocation_(frame: v8.FrameMirror): stackdriver.SourceLocation { - return { - path: this.resolveRelativePath_(frame), - // V8 uses 0-based line numbers but Debuglet API uses 1-based numbers. - line: frame.sourceLine() + 1, - }; - } - - /** - * Iterates and returns variable information for all scopes (excluding global) - * in a given frame. FrameMirrors should return their scope object list with - * most deeply nested scope first so variables initially encountered will take - * precedence over subsequent instance with the same name - this is tracked in - * the usedNames map. The argument list given to this function may be - * manipulated if variables with a deeper scope occur which have the same - * name. - * @function resolveLocalsList_ - * @memberof StateResolver - * @param {FrameMirror} frame - A instance of FrameMirror - * @param {Array} args - An array of objects representing any function - * arguments the frame may list - * @returns {Array} - returns an array containing data about selected - * variables - */ - resolveLocalsList_(frame: v8.FrameMirror): stackdriver.Variable[] { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const self = this; - const usedNames: {[name: string]: boolean} = {}; - const makeMirror = this.ctx.MakeMirror; - const allScopes = frame.allScopes(); - const count = allScopes.length; - - // There will always be at least 3 scopes. - // For top-level breakpoints: [local, script, global] - // Other: [..., closure (module IIFE), script, global] - assert.ok(count >= 3); - assert.strictEqual(allScopes[count - 1].scopeType(), self.scopeType.Global); - assert.strictEqual(allScopes[count - 2].scopeType(), self.scopeType.Script); - - // We find the top-level (module global) variable pollute the local - // variables we omit them by default, unless the breakpoint itself is - // top-level. The last two scopes are always omitted. - let scopes: v8.ScopeMirror[]; - if (allScopes[count - 3].scopeType() === self.scopeType.Closure) { - scopes = allScopes.slice(0, -3); - } else { - assert.ok(allScopes[count - 3].scopeType() === self.scopeType.Local); - scopes = allScopes.slice(0, -2); - } - - const fromScopes = scopes.map((scope: v8.ScopeMirror) => { - const obj = scope.details().object(); - return Object.keys(obj).reduce((acc, name) => { - const value = obj[name]; - const trg = makeMirror(value); - if (!usedNames[name]) { - // It's a valid variable that belongs in the locals list - // and wasn't discovered at a lower-scope - usedNames[name] = true; - // TODO: Determine how to not have an explicit down cast to - // ValueMirror - acc.push(self.resolveVariable_(name, trg as v8.ValueMirror, false)); - } - return acc; - }, [] as stackdriver.Variable[]); - }); - - function resolveFromReceiver(): stackdriver.Variable[] { - // The frame receiver is the 'this' context that is present during - // invocation. Check to see whether a receiver context is substantive, - // (invocations may be bound to null) if so: store in the locals list - // under the name 'context' which is used by the Chrome DevTools. - const ctx = frame.details().receiver(); - if (ctx) { - // TODO: Determine how to not have an explicit down cast to - // ValueMirror - return [ - self.resolveVariable_( - 'context', - makeMirror(ctx) as v8.ValueMirror, - false - ), - ]; - } - return []; - } - - return ([] as stackdriver.Variable[]) - .concat(...fromScopes) - .concat(resolveFromReceiver()); - } - - /** - * Computes a text representation of the provided value based on its type. - * If the value is a recursive data type, it will be represented as an index - * into the variable table. - * - * @param {String} name The name of the variable. - * @param {Object} value A v8 debugger representation of a variable value. - * @param {boolean} isEvaluated Specifies if the variable is from a watched - * expression. - */ - resolveVariable_( - name: string, - value: v8.ValueMirror, - isEvaluated: boolean - ): stackdriver.Variable { - let size = name.length; - - const data: stackdriver.Variable = {name}; - - if (value.isPrimitive() || value.isRegExp()) { - // primitives: undefined, null, boolean, number, string, symbol - data.value = value.toText(); - const maxLength = this.config.capture.maxStringLength; - if (!isEvaluated && maxLength && maxLength < data.value.length) { - data.status = new StatusMessage( - StatusMessage.VARIABLE_VALUE, - 'Only first `config.capture.maxStringLength=' + - this.config.capture.maxStringLength + - '` chars were captured for string of length ' + - data.value.length + - '. Use in an expression to see the full string.', - false - ); - data.value = data.value.substring(0, maxLength) + '...'; - } - } else if (value.isFunction()) { - // TODO: Determine how to resolve this so that a ValueMirror doesn't need - // to be cast to a FunctionMirror. - data.value = - 'function ' + - this.resolveFunctionName_(value as v8.FunctionMirror) + - '()'; - } else if (value.isObject()) { - data.varTableIndex = this.getVariableIndex_(value); - } else { - // PropertyMirror, InternalPropertyMirror, FrameMirror, ScriptMirror - data.value = 'unknown mirror type'; - } - - if (data.value) { - size += data.value.length; - } else { - size += 8; // fudge-it - } - - this.totalSize += size; - - return data; - } - - getVariableIndex_(valueMirror: v8.ValueMirror): number { - let idx = this.rawVariableTable.findIndex( - rawVar => !!rawVar && rawVar.value() === valueMirror.value() - ); - if (idx === -1) { - idx = this.storeObjectToVariableTable_(valueMirror); - } - return idx; - } - - storeObjectToVariableTable_(obj: v8.ValueMirror): number { - const idx = this.rawVariableTable.length; - this.rawVariableTable[idx] = obj; - return idx; - } - - /** - * Responsible for recursively resolving the properties on a - * provided object mirror. - */ - resolveMirror_( - mirror: v8.ObjectMirror, - isEvaluated: boolean - ): stackdriver.Variable { - let properties = mirror.properties(); - const maxProps = this.config.capture.maxProperties; - const truncate = maxProps && properties.length > maxProps; - if (!isEvaluated && truncate) { - properties = properties.slice(0, maxProps); - } - // TODO: It looks like `members` should be of type stackdriver.Variable[] - // but is missing fields. Determine if those fields are required or - // if the type should not be stackdriver.Variable[] - const members = properties.map( - this.resolveMirrorProperty_.bind(this, isEvaluated) - ); - if (!isEvaluated && truncate) { - // TDOO: Determine how to remove this explicit cast - members.push({ - name: - 'Only first `config.capture.maxProperties=' + - this.config.capture.maxProperties + - '` properties were captured. Use in an expression' + - ' to see all properties.', - }); - } - return {value: mirror.toText(), members}; - } - - resolveMirrorProperty_( - isEvaluated: boolean, - property: v8.PropertyMirror - ): stackdriver.Variable { - const name = String(property.name()); - // Array length must be special cased as it is a native property that - // we know to be safe to evaluate which is not generally true. - const isArrayLen = property.mirror_.isArray() && name === 'length'; - if (property.isNative() && !isArrayLen) { - return {name, varTableIndex: NATIVE_PROPERTY_MESSAGE_INDEX}; - } - if (property.hasGetter()) { - return {name, varTableIndex: GETTER_MESSAGE_INDEX}; - } - return this.resolveVariable_(name, property.value(), isEvaluated); - } -} - -// This function is used by unit tests to make sure assertions are enabled. -export function testAssert(): void { - assert.strictEqual(0, 1); -} - -/** - * Captures the stack and current execution state. - * - * @return an object with stackFrames, variableTable, and - * evaluatedExpressions fields - */ -export function capture( - execState: v8.ExecutionState, - expressions: string[], - config: ResolvedDebugAgentConfig, - v8debug: v8.Debug -): stackdriver.Breakpoint { - return new StateResolver(execState, expressions, config, v8debug).capture_(); -} diff --git a/test/debugger.ts b/test/debugger.ts deleted file mode 100644 index 3945b38f..00000000 --- a/test/debugger.ts +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2016 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/debugger - */ - -// import {Debuggee} from '../src/debuggee'; -import * as stackdriver from '../src/types/stackdriver'; - -// TODO: Write a Debugger interface that works with the Firebase backend. - -export class Debugger { - /** - * @constructor - */ - constructor() {} - - /** - * Gets a list of debuggees in a given project to which the user can set - * breakpoints. - * @param {string} projectId - The project ID for the project whose debuggees - * should be listed. - * @param {boolean=} includeInactive - Whether or not to include inactive - * debuggees in the list (default false). - */ - async listDebuggees(projectId: string, includeInactive: boolean) {} - - /** - * Gets a list of breakpoints in a given debuggee. - * @param {string} debuggeeId - The ID of the debuggee whose breakpoints should - * be listed. - * @param {object=} options - An object containing options on the list of - * breakpoints. - * @param {boolean=} options.includeAllUsers - If set to true, include - * breakpoints set by all users, or just by the caller (default false). - * @param {boolean=} options.includeInactive - Whether or not to include - * 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. - */ - listBreakpoints( - debuggeeId: string, - options: { - includeAllUsers?: boolean; - includeInactive?: boolean; - action?: stackdriver.Action; - } - ) {} - - /** - * Gets information about a given breakpoint. - * @param {string} debuggee - The ID of the debuggee in which the breakpoint - * is set. - * @param {string} breakpointId - The ID of the breakpoint to get information - * about. - */ - getBreakpoint(debuggeeId: string, breakpointId: string) {} - - /** - * Sets a new breakpoint. - * @param {Debuggee} debuggeeId - The ID of the debuggee in which the breakpoint - * should be set. - * @param {Breakpoint} breakpoint - An object representing information about - * the breakpoint to set. - */ - 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. - */ - deleteBreakpoint(debuggeeId: string, breakpointId: string) {} -} diff --git a/test/test-debuglet.ts b/test/test-debuglet.ts index 53e4ed4c..0c0bde42 100644 --- a/test/test-debuglet.ts +++ b/test/test-debuglet.ts @@ -12,73 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as assert from 'assert'; -import {after, afterEach, before, beforeEach, describe, it} from 'mocha'; -import * as fs from 'fs'; -import * as gcpMetadata from 'gcp-metadata'; +import {describe, it} from 'mocha'; import * as path from 'path'; -import * as proxyquire from 'proxyquire'; -import {DebugAgentConfig, ResolvedDebugAgentConfig} from '../src/agent/config'; import {defaultConfig as DEFAULT_CONFIG} from '../src/agent/config'; -import {Debuggee} from '../src/debuggee'; -import * as stackdriver from '../src/types/stackdriver'; DEFAULT_CONFIG.allowExpressions = true; DEFAULT_CONFIG.workingDirectory = path.join(__dirname, '..', '..'); -import { - Debuglet, - CachedPromise, - FindFilesResult, - Platforms, -} from '../src/agent/debuglet'; -import {ScanResults} from '../src/agent/io/scanner'; -import * as extend from 'extend'; - -const DEBUGGEE_ID = 'bar'; -const REGISTER_PATH = '/v2/controller/debuggees/register'; -const BPS_PATH = '/v2/controller/debuggees/' + DEBUGGEE_ID + '/breakpoints'; -const EXPRESSIONS_REGEX = - /Expressions and conditions are not allowed.*https:\/\/goo\.gl\/ShSm6r/; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const fakeCredentials = require('./fixtures/gcloud-credentials.json'); - -const packageInfo = { - name: 'Some name', - version: 'Some version', -}; +import {CachedPromise} from '../src/agent/debuglet'; import * as nock from 'nock'; -import * as nocks from './nocks'; nock.disableNetConnect(); -const defaultConfig = extend(true, {}, DEFAULT_CONFIG, {logLevel: 0}); - -let oldGP: string | undefined; - -// TODO: Have this actually implement Breakpoint. -const bp: stackdriver.Breakpoint = { - id: 'test', - action: 'CAPTURE', - location: {path: 'build/test/fixtures/foo.js', line: 2}, -} as stackdriver.Breakpoint; -// TODO: Have this actually implement Breakpoint. -const errorBp: stackdriver.Breakpoint = { - id: 'testLog', - action: 'FOO', - location: {path: 'build/test/fixtures/foo.js', line: 2}, -} as {} as stackdriver.Breakpoint; - -function verifyBreakpointRejection( - re: RegExp, - body: {breakpoint: stackdriver.Breakpoint} -) { - const status = body.breakpoint.status; - const hasCorrectDescription = !!status!.description.format.match(re); - return status!.isError && hasCorrectDescription; -} - describe('CachedPromise', () => { it('CachedPromise.get() will resolve after CachedPromise.resolve()', function (done) { this.timeout(2000); @@ -89,1725 +34,3 @@ describe('CachedPromise', () => { cachedPromise.resolve(); }); }); - -// 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; -// } diff --git a/test/test-module.ts b/test/test-module.ts deleted file mode 100644 index c73538ab..00000000 --- a/test/test-module.ts +++ /dev/null @@ -1,71 +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 * as assert from 'assert'; -import {before, describe, it} from 'mocha'; -const m: NodeModule & { - start: Function; - get: () => Debuglet | undefined; - // eslint-disable-next-line @typescript-eslint/no-var-requires -} = require('../..'); -import * as nock from 'nock'; -import * as nocks from './nocks'; -import {Debuglet} from '../src/agent/debuglet'; - -nock.disableNetConnect(); - -// TODO: Re-enable once there's mocking that allows firebase client to connect. - -// 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 throw on attempt to start a new agent', () => { -// assert.throws(() => { -// m.start(); -// }); -// }); - -// 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 deleted file mode 100644 index 23f481b5..00000000 --- a/test/test-options-credentials.ts +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2016 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 assert from 'assert'; -import {afterEach, beforeEach, describe, it} from 'mocha'; -import * as extend from 'extend'; -import * as nock from 'nock'; -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 * as nocks from './nocks'; - -const envProject = process.env.GCLOUD_PROJECT; -const packageInfo = { - name: 'Some name', - version: 'Some version', -}; - -nock.disableNetConnect(); - -// TODO: Write tests that verify the choice of credentials when using Firebase. - -// describe('test-options-credentials', () => { -// let debuglet: Debuglet | null = null; - -// beforeEach(() => { -// delete process.env.GCLOUD_PROJECT; -// assert.strictEqual(debuglet, null); -// }); - -// afterEach(() => { -// assert.ok(debuglet); -// debuglet!.stop(); -// debuglet = null; -// process.env.GCLOUD_PROJECT = envProject; -// }); - -// 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 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(); -// }); -// }); diff --git a/test/test-state.ts b/test/test-state.ts deleted file mode 100644 index 0c0ea4c9..00000000 --- a/test/test-state.ts +++ /dev/null @@ -1,53 +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 assert from 'assert'; -import {describe, it} from 'mocha'; -import * as utils from '../src/agent/util/utils'; -import * as proxyquire from 'proxyquire'; - -const describeFn = utils.satisfies(process.version, '>=10') - ? describe.skip - : describe; - -describeFn('state', () => { - // Testing of state.js is driven through test-v8debugapi.js. There are - // minimal unit tests here. - - it('should have assertions enabled', () => { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const state = require('../src/agent/state/legacy-state'); - - // this test makes sure that the necessary environment variables to enable - // asserts are present during testing. Use run-tests.sh, or export - // CLOUD_DEBUG_ASSERTIONS=1 to make sure this test passes. - if (!process.env.CLOUD_DEBUG_ASSERTIONS) { - console.log( - 'This test requires the enviornment variable ' + - 'CLOUD_DEBUG_ASSERTIONS to be set in order to pass' - ); - } - assert.throws(() => { - state.testAssert(); - }); - }); - - it('should not throw if vm is not an object', () => { - // test for - // https://github.com/googleapis/cloud-debug-nodejs/issues/503 - // eslint-disable-next-line @typescript-eslint/no-var-requires - const state = proxyquire('../src/agent/state/legacy-state', {vm: false}); - assert.ok(state); - }); -});