Skip to content
This repository has been archived by the owner on Apr 3, 2024. It is now read-only.

Commit

Permalink
feat: pathResolver to allow arbitrary path mapping on behalf of agent (
Browse files Browse the repository at this point in the history
…#461)

Fixes #391
  • Loading branch information
dylanscott authored and DominicKramer committed Sep 12, 2018
1 parent 4099dea commit a0a27f0
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 12 deletions.
53 changes: 53 additions & 0 deletions src/agent/config.ts
Expand Up @@ -164,6 +164,58 @@ export interface ResolvedDebugAgentConfig extends GoogleAuthOptions {
*/
appPathRelativeToRepository?: string;

/**
* A function which takes the path of a source file in your repository,
* a list of your project's Javascript files known to the debugger,
* and the file(s) in your project that the debugger thinks is identified
* by the given path.
*
* This function should return the file(s) that is/are identified by the
* given path or `undefined` to specify that the files(s) that the agent
* thinks are associated with the file should be used.
*
* Note that the list of paths must be a subset of the files in `knownFiles`
* and the debug agent can set a breakpoint for the input path if and only
* if there is a unique file that this function returns (an array with
* exactly one entry).
*
* This configuration option is usually unecessary, but can be useful in
* situations where the debug agent cannot not identify the file in your
* application associated with a path.
*
* This could occur if your application uses a structure that the debug
* agent does not understand, or if more than one file in your application
* has the same name.
*
* For example, if your running application (either locally or in the cloud)
* has the Javascript files:
* /x/y/src/index.js
* /x/y/src/someDir/index.js
* /x/y/src/util.js
* and a breakpoint is set in the `/x/y/src/index.js` through the cloud
* console, the `appResolver` function would be invoked with the following
* arguments:
* scriptPath: 'index.js'
* knownFiles: ['/x/y/src/index.js',
* '/x/y/src/someDir/index.js',
* '/x/y/src/util.js']
* resolved: ['/x/y/src/index.js',
* '/x/y/src/someDir/index.js']
* This is because out of the known files, the files, '/x/y/src/index.js'
* and '/x/y/src/someDir/index.js' end with 'index.js'.
*
* If the array `['/x/y/src/index.js', '/x/y/src/someDir/index.js']` or
* equivalently `undefined` is returned by the `pathResolver` function, the
* debug agent will not be able to set the breakpoint.
*
* If, however, the `pathResolver` function returned `['/x/y/src/index.js']`,
* for example, the debug agent would know to set the breakpoint in
* the `/x/y/src/index.js` file.
*/
pathResolver?:
(scriptPath: string, knownFiles: string[],
resolved: string[]) => string[] | undefined;

/**
* agent log level 0-disabled, 1-error, 2-warn, 3-info, 4-debug
*/
Expand Down Expand Up @@ -282,6 +334,7 @@ export const defaultConfig: ResolvedDebugAgentConfig = {
{service: undefined, version: undefined, minorVersion_: undefined},

appPathRelativeToRepository: undefined,
pathResolver: undefined,
logLevel: 1,
breakpointUpdateIntervalSec: 10,
breakpointExpirationSec: 60 * 60 * 24, // 24 hours
Expand Down
48 changes: 48 additions & 0 deletions src/agent/util/utils.ts
@@ -1,9 +1,12 @@
import * as _ from 'lodash';
import * as path from 'path';
import * as semver from 'semver';

import {StatusMessage} from '../../client/stackdriver/status-message';
import * as stackdriver from '../../types/stackdriver';

import consoleLogLevel = require('console-log-level');

import {ResolvedDebugAgentConfig} from '../config';
import {ScanStats} from '../io/scanner';

Expand Down Expand Up @@ -34,6 +37,51 @@ export interface Listener {
}
// Exposed for unit testing.
export function findScripts(
scriptPath: string, config: ResolvedDebugAgentConfig, fileStats: ScanStats,
logger: consoleLogLevel.Logger): string[] {
// (path: string, knownFiles: string[], resolved: string[]) => string[]
const resolved = resolveScripts(scriptPath, config, fileStats);
if (config.pathResolver) {
if (!_.isFunction(config.pathResolver)) {
logger.warn(
`The 'pathResolver' config must be a function. Continuing ` +
`with the agent's default behavior.`);
return resolved;
}

const knownFiles = Object.keys(fileStats);
const calculatedPaths =
config.pathResolver(scriptPath, knownFiles, resolved);
if (calculatedPaths === undefined) {
return resolved;
}

if (!calculatedPaths || !Array.isArray(calculatedPaths)) {
logger.warn(
`The 'pathResolver' config function returned a value ` +
`other than 'undefined' or an array of strings. Continuing with ` +
`the agent's default behavior.`);
return resolved;
}

for (const path of calculatedPaths) {
if (knownFiles.indexOf(path) === -1) {
logger.warn(
`The 'pathResolver' config function returned a path ` +
`'${path}' that is not in the list of paths known to the debug agent ` +
JSON.stringify(knownFiles, null, 2) +
` only known paths can be returned. Continuing with the agent's ` +
`default behavior.`);
return resolved;
}
}

return calculatedPaths;
}
return resolved;
}

function resolveScripts(
scriptPath: string, config: ResolvedDebugAgentConfig,
fileStats: ScanStats): string[] {
// Use repository relative mapping if present.
Expand Down
2 changes: 1 addition & 1 deletion src/agent/v8/inspector-debugapi.ts
Expand Up @@ -294,7 +294,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
mapInfo ? mapInfo.file :
path.normalize(
(breakpoint.location as stackdriver.SourceLocation).path),
this.config, this.fileStats);
this.config, this.fileStats, this.logger);
if (scripts.length === 0) {
return utils.setErrorStatusAndCallback(
cb, breakpoint, StatusMessage.BREAKPOINT_SOURCE_LOCATION,
Expand Down
2 changes: 1 addition & 1 deletion src/agent/v8/legacy-debugapi.ts
Expand Up @@ -276,7 +276,7 @@ export class V8DebugApi implements debugapi.DebugApi {
mapInfo ? mapInfo.file :
path.normalize(
(breakpoint.location as stackdriver.SourceLocation).path),
this.config, this.fileStats);
this.config, this.fileStats, this.logger);
if (scripts.length === 0) {
return utils.setErrorStatusAndCallback(
cb, breakpoint, StatusMessage.BREAKPOINT_SOURCE_LOCATION,
Expand Down
61 changes: 61 additions & 0 deletions test/mock-logger.ts
@@ -0,0 +1,61 @@
/**
* Copyright 2018 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 consoleLogLevel = require('console-log-level');

export type Arguments = string[];
export interface Call {
type: 'trace'|'debug'|'info'|'warn'|'error'|'fatal';
args: Arguments;
}

export class MockLogger implements consoleLogLevel.Logger {
traces: Call[] = [];
debugs: Call[] = [];
infos: Call[] = [];
warns: Call[] = [];
errors: Call[] = [];
fatals: Call[] = [];

allCalls() {
return this.traces.concat(
this.debugs, this.infos, this.warns, this.errors, this.fatals);
}

trace(...args: Arguments) {
this.traces.push({type: 'trace', args});
}

debug(...args: Arguments) {
this.debugs.push({type: 'debug', args});
}

info(...args: Arguments) {
this.infos.push({type: 'info', args});
}

warn(...args: Arguments) {
this.warns.push({type: 'warn', args});
}

error(...args: Arguments) {
this.errors.push({type: 'error', args});
}

fatal(...args: Arguments) {
this.fatals.push({type: 'fatal', args});
}
}

0 comments on commit a0a27f0

Please sign in to comment.