This commit is contained in:
Eric Cornelson 2019-07-08 11:32:47 -07:00
Родитель 9929d7d146
Коммит ae0e8df977
120 изменённых файлов: 30875 добавлений и 678 удалений

Просмотреть файл

@ -5,28 +5,31 @@
import * as assert from 'assert';
import * as path from 'path';
import { createServer } from 'http-server';
import { createServer, } from 'http-server';
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
import * as testSetup from './testSetup';
import { HttpOrHttpsServer } from './types/server';
import { isWindows } from './testSetup';
import * as puppeteer from 'puppeteer';
import { expect } from 'chai';
import { killAllChrome } from '../testUtils';
import { IAttachRequestArgs } from 'vscode-chrome-debug-core';
import { getDebugAdapterLogFilePath } from './utils/logging';
const DATA_ROOT = testSetup.DATA_ROOT;
suite('Chrome Debug Adapter etc', () => {
let dc: ExtendedDebugClient;
let server;
let server: HttpOrHttpsServer | null;
setup(() => {
return testSetup.setup()
setup(function () {
return testSetup.setup(this)
.then(_dc => dc = _dc);
});
teardown(() => {
if (server) {
server.close();
}
return testSetup.teardown();
});
@ -43,14 +46,34 @@ suite('Chrome Debug Adapter etc', () => {
suite('initialize', () => {
test('should return supported features', () => {
return dc.initializeRequest().then(response => {
assert.equal(response.body.supportsConfigurationDoneRequest, true);
assert.notEqual(response.body, undefined);
assert.equal(response.body!.supportsConfigurationDoneRequest, true);
});
});
});
suite('launch', () => {
test('should stop on debugger statement in file:///, sourcemaps disabled', () => {
const testProjectRoot = path.join(DATA_ROOT, 'intervalDebugger');
const testProjectRoot = path.join(DATA_ROOT, 'intervalDebugger');
setup(() => {
server = createServer({ root: testProjectRoot });
server.listen(7890);
});
teardown(() => {
if (server) {
server.close(err => console.log('Error closing server in teardown: ' + (err && err.message)));
server = null;
}
});
/**
* On MacOS it fails because: stopped location: path mismatch:
* + expected: /users/vsts/agent/2.150.0/work/1/s/testdata/intervaldebugger/out/app.js
* - actual: users/vsts/agent/2.150.0/work/1/s/testdata/intervaldebugger/out/app.js
*/
(isWindows ? test : test.skip)('should stop on debugger statement in file:///, sourcemaps disabled', () => {
const launchFile = path.join(testProjectRoot, 'index.html');
const breakFile = path.join(testProjectRoot, 'out/app.js');
const DEBUGGER_LINE = 2;
@ -63,13 +86,9 @@ suite('Chrome Debug Adapter etc', () => {
});
test('should stop on debugger statement in http://localhost', () => {
const testProjectRoot = path.join(DATA_ROOT, 'intervalDebugger');
const breakFile = path.join(testProjectRoot, 'src/app.ts');
const DEBUGGER_LINE = 2;
server = createServer({ root: testProjectRoot });
server.listen(7890);
return Promise.all([
dc.configurationSequence(),
dc.launch({ url: 'http://localhost:7890', webRoot: testProjectRoot }),
@ -77,52 +96,94 @@ suite('Chrome Debug Adapter etc', () => {
]);
});
test('Should hit breakpoint even if webRoot has unexpected case all uppercase for VisualStudio', async () => {
const testProjectRoot = path.join(DATA_ROOT, 'breakOnLoad_javaScript');
const breakFile = path.join(testProjectRoot, 'src/script.js');
const DEBUGGER_LINE = 3;
const testTitle = 'Should attach to existing instance of chrome and break on debugger statement';
test(testTitle, async () => {
const fullTestTitle = `Chrome Debug Adapter etc launch ${testTitle}`;
const breakFile = path.join(testProjectRoot, 'src/app.ts');
const DEBUGGER_LINE = 2;
const remoteDebuggingPort = 7777;
const server = createServer({ root: testProjectRoot });
const browser = await puppeteer.launch({ headless: false, args: ['http://localhost:7890', `--remote-debugging-port=${remoteDebuggingPort}`] });
try {
server.listen(7890);
await dc.initializeRequest({
adapterID: 'chrome',
clientID: 'visualstudio',
linesStartAt1: true,
columnsStartAt1: true,
pathFormat: 'path'
});
await dc.launchRequest({ url: 'http://localhost:7890', webRoot: testProjectRoot.toUpperCase() } as any);
await dc.setBreakpointsRequest({ source: { path: breakFile }, breakpoints: [{ line: DEBUGGER_LINE }] });
await dc.configurationDoneRequest();
await dc.assertStoppedLocation('breakpoint', { path: breakFile, line: DEBUGGER_LINE } );
} finally {
server.close();
await Promise.all([
dc.configurationSequence(),
dc.initializeRequest().then(_ => {
return dc.attachRequest(<IAttachRequestArgs>{
url: 'http://localhost:7890', port: remoteDebuggingPort, webRoot: testProjectRoot,
logFilePath: getDebugAdapterLogFilePath(fullTestTitle), logTimestamps: true
});
}),
dc.assertStoppedLocation('debugger_statement', { path: breakFile, line: DEBUGGER_LINE } )
]);
}
finally {
await browser.close();
}
});
test('Should hit breakpoint even if webRoot has unexpected case all lowercase for VisualStudio', async () => {
const testProjectRoot = path.join(DATA_ROOT, 'breakOnLoad_javaScript');
const breakFile = path.join(testProjectRoot, 'src/script.js');
const DEBUGGER_LINE = 3;
const breakFile = path.join(testProjectRoot, 'src/app.ts');
const DEBUGGER_LINE = 2;
await dc.initializeRequest({
adapterID: 'chrome',
clientID: 'visualstudio',
linesStartAt1: true,
columnsStartAt1: true,
pathFormat: 'path'
});
await dc.launchRequest( { url: 'http://localhost:7890', webRoot: testProjectRoot.toLowerCase(), runtimeExecutable: puppeteer.executablePath() } as any);
await dc.setBreakpointsRequest({ source: { path: breakFile }, breakpoints: [{ line: DEBUGGER_LINE }] });
await dc.configurationDoneRequest();
await dc.assertStoppedLocation('debugger_statement', { path: breakFile, line: DEBUGGER_LINE } );
});
test('Should hit breakpoint even if webRoot has unexpected case all uppercase for VisualStudio', async () => {
const breakFile = path.join(testProjectRoot, 'src/app.ts');
const DEBUGGER_LINE = 2;
await dc.initializeRequest({
adapterID: 'chrome',
clientID: 'visualstudio',
linesStartAt1: true,
columnsStartAt1: true,
pathFormat: 'path'
});
await dc.launchRequest({ url: 'http://localhost:7890', webRoot: testProjectRoot.toUpperCase(), runtimeExecutable: puppeteer.executablePath() } as any);
await dc.setBreakpointsRequest({ source: { path: breakFile }, breakpoints: [{ line: DEBUGGER_LINE }] });
await dc.configurationDoneRequest();
await dc.assertStoppedLocation('debugger_statement', { path: breakFile, line: DEBUGGER_LINE } );
});
/**
* This test is baselining behvaior from V1 around what happens when the adapter tries to launch when
* there is another running instance of chrome with --remote-debugging-port set to the same port the adapter is trying to use.
* We expect the debug adapter to throw an exception saying that the connection attempt timed out after N milliseconds.
* TODO: We don't think is is ideal behavior for the adapter, and want to change it fairly quickly after V2 is ready to launch.
* right now this test exists only to verify that we match the behavior of V1
*/
test('Should throw error when launching if chrome debug port is in use', async () => {
// browser already launched to the default port, and navigated away from about:blank
const remoteDebuggingPort = 9222;
const browser = await puppeteer.launch({ headless: false, args: ['http://localhost:7890', `--remote-debugging-port=${remoteDebuggingPort}`] });
const server = createServer({ root: testProjectRoot });
try {
server.listen(7890);
await dc.initializeRequest({
adapterID: 'chrome',
clientID: 'visualstudio',
linesStartAt1: true,
columnsStartAt1: true,
pathFormat: 'path'
});
await dc.launchRequest({ url: 'http://localhost:7890', webRoot: testProjectRoot.toLowerCase() } as any);
await dc.setBreakpointsRequest({ source: { path: breakFile }, breakpoints: [{ line: DEBUGGER_LINE }] });
await dc.configurationDoneRequest();
await dc.assertStoppedLocation('breakpoint', { path: breakFile, line: DEBUGGER_LINE } );
} finally {
server.close();
await Promise.all([
dc.configurationSequence(),
dc.launch({ url: 'http://localhost:7890', timeout: 2000, webRoot: testProjectRoot, port: remoteDebuggingPort }),
]);
assert.fail('Expected launch to throw a timeout exception, but it didn\'t.');
} catch (err) {
expect(err.message).to.satisfy( (x: string) => x.startsWith('Cannot connect to runtime process, timeout after 2000 ms'));
}
finally {
await browser.close();
}
// force kill chrome here, as it will be left open by the debug adapter (same behavior as v1)
killAllChrome();
});
});
});

Просмотреть файл

@ -10,20 +10,22 @@ import * as ts from 'vscode-chrome-debug-core-testsupport';
import * as testSetup from './testSetup';
import { BreakOnLoadStrategy } from 'vscode-chrome-debug-core';
import { HttpOrHttpsServer } from './types/server';
function runCommonTests(breakOnLoadStrategy: BreakOnLoadStrategy) {
const DATA_ROOT = testSetup.DATA_ROOT;
let dc: ts.ExtendedDebugClient;
setup(() => {
return testSetup.setup(undefined, { breakOnLoadStrategy: breakOnLoadStrategy })
setup(function () {
return testSetup.setup(this, undefined, { breakOnLoadStrategy: breakOnLoadStrategy })
.then(_dc => dc = _dc);
});
let server: any;
let server: HttpOrHttpsServer | null;
teardown(() => {
if (server) {
server.close();
server.close(err => console.log('Error closing server in teardown: ' + (err && err.message)));
server = null;
}
return testSetup.teardown();
@ -35,15 +37,16 @@ function runCommonTests(breakOnLoadStrategy: BreakOnLoadStrategy) {
// https://github.com/Microsoft/vscode-chrome-debug-core/blob/90797bc4a3599b0a7c0f197efe10ef7fab8442fd/src/chrome/chromeDebugAdapter.ts#L692
// so we don't want to use hitBreakpointUnverified function because it specifically checks for 'breakpoint' as the reason
function launchWithUrlAndSetBreakpoints(url: string, projectRoot: string, scriptPath: string, lineNum: number, colNum: number): Promise<any> {
const waitForInitialized = dc.waitForEvent('initialized');
return Promise.all([
dc.launch({ url: url, webRoot: projectRoot }),
dc.waitForEvent('initialized').then(event => {
waitForInitialized.then(_event => {
return dc.setBreakpointsRequest({
lines: [lineNum],
breakpoints: [{ line: lineNum, column: colNum }],
source: { path: scriptPath }
});
}).then(response => {
}).then(_response => {
return dc.configurationDoneRequest();
})
]);
@ -97,7 +100,7 @@ function runCommonTests(breakOnLoadStrategy: BreakOnLoadStrategy) {
if (breakOnLoadStrategy === 'instrument') {
await launchWithUrlAndSetBreakpoints(url, testProjectRoot, scriptPath, bpLine, bpCol);
await dc.assertStoppedLocation('debugger_statement', { path: scriptPath, line: bpLine, column: bpCol });
await dc.assertStoppedLocation('breakpoint', { path: scriptPath, line: bpLine, column: bpCol });
} else {
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol });
}
@ -168,7 +171,7 @@ function runCommonTests(breakOnLoadStrategy: BreakOnLoadStrategy) {
if (breakOnLoadStrategy === 'instrument') {
await launchWithUrlAndSetBreakpoints(url, testProjectRoot, scriptPath, bpLine, bpCol);
await dc.assertStoppedLocation('debugger_statement', { path: scriptPath, line: bpLine, column: bpCol });
await dc.assertStoppedLocation('breakpoint', { path: scriptPath, line: bpLine, column: bpCol });
} else {
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol });
}
@ -205,14 +208,14 @@ function runCommonTests(breakOnLoadStrategy: BreakOnLoadStrategy) {
if (breakOnLoadStrategy === 'instrument') {
await launchWithUrlAndSetBreakpoints(url, testProjectRoot, scriptPath, bpLine, bpCol);
await dc.assertStoppedLocation('debugger_statement', { path: scriptPath, line: bpLine, column: bpCol });
await dc.assertStoppedLocation('breakpoint', { path: scriptPath, line: bpLine, column: bpCol });
await dc.setBreakpointsRequest({
lines: [bpLine],
breakpoints: [{ line: bpLine, column: bpCol }],
source: { path: script2Path }
});
await dc.continueRequest();
await dc.assertStoppedLocation('debugger_statement', { path: script2Path, line: bpLine, column: bpCol });
await dc.assertStoppedLocation('breakpoint', { path: script2Path, line: bpLine, column: bpCol });
} else {
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol });
await dc.setBreakpointsRequest({
@ -240,8 +243,8 @@ suite('BreakOnLoad', () => {
suite('Instrument Webpack Project', () => {
let dc: ts.ExtendedDebugClient;
setup(() => {
return testSetup.setup(undefined, { breakOnLoadStrategy: 'instrument' })
setup(function () {
return testSetup.setup(this, undefined, { breakOnLoadStrategy: 'instrument' })
.then(_dc => dc = _dc);
});
@ -249,6 +252,7 @@ suite('BreakOnLoad', () => {
teardown(() => {
if (server) {
server.close();
server = null;
}
return testSetup.teardown();
@ -266,7 +270,7 @@ suite('BreakOnLoad', () => {
const bpLine = 3;
const bpCol = 1;
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine , column: bpCol});
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol });
});
test('Hits multiple breakpoints in a file on load', async () => {
@ -307,8 +311,8 @@ suite('BreakOnLoad', () => {
suite('BreakOnLoad Disabled (strategy: off)', () => {
let dc: ts.ExtendedDebugClient;
setup(() => {
return testSetup.setup(undefined, { breakOnLoadStrategy: 'off' })
setup(function () {
return testSetup.setup(this, undefined, { breakOnLoadStrategy: 'off' })
.then(_dc => dc = _dc);
});
@ -316,6 +320,7 @@ suite('BreakOnLoad', () => {
teardown(() => {
if (server) {
server.close();
server = null;
}
return testSetup.teardown();
@ -328,13 +333,11 @@ suite('BreakOnLoad', () => {
server = createServer({ root: testProjectRoot });
server.listen(7890);
const url = 'http://localhost:7890/index.html';
// We try to put a breakpoint at (1,1). If this doesn't get hit, the console.log statement in the script should be executed
const bpLine = 1;
const bpCol = 1;
return new Promise( (resolve, reject) => {
return new Promise( (resolve, _reject) => {
// Add an event listener for the output event to capture the console.log statement
dc.addListener('output', function(event) {
// If console.log event statement is executed, pass the test
@ -343,13 +346,13 @@ suite('BreakOnLoad', () => {
}
}),
Promise.all([
dc.waitForEvent('initialized').then(event => {
dc.waitForEvent('initialized').then(_event => {
return dc.setBreakpointsRequest({
lines: [bpLine],
breakpoints: [{ line: bpLine, column: bpCol }],
source: { path: scriptPath }
});
}).then(response => {
}).then(_response => {
return dc.configurationDoneRequest();
}),

Просмотреть файл

@ -9,23 +9,25 @@ import { createServer } from 'http-server';
import * as ts from 'vscode-chrome-debug-core-testsupport';
import * as testSetup from './testSetup';
import { HttpOrHttpsServer } from './types/server';
suite('Breakpoints', () => {
const DATA_ROOT = testSetup.DATA_ROOT;
let dc: ts.ExtendedDebugClient;
setup(() => {
return testSetup.setup()
setup(function () {
return testSetup.setup(this)
.then(_dc => dc = _dc);
});
let server: any;
teardown(() => {
let server: HttpOrHttpsServer | null;
teardown(async () => {
if (server) {
server.close();
server.close(err => console.log('Error closing server in teardown: ' + (err && err.message)));
server = null;
}
return testSetup.teardown();
await testSetup.teardown();
});
suite('Column BPs', () => {
@ -39,6 +41,7 @@ suite('Breakpoints', () => {
const url = 'http://localhost:7890/index.html';
const bpLine = 4;
const bpCol = 16;
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol });
});
@ -58,6 +61,7 @@ suite('Breakpoints', () => {
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol1 });
await dc.setBreakpointsRequest({ source: { path: scriptPath }, breakpoints: [{ line: bpLine, column: bpCol2 }] });
await dc.continueTo('breakpoint', { line: bpLine, column: bpCol2 });
});
test('BP col is adjusted to correct col', async () => {

Просмотреть файл

@ -0,0 +1,8 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
export function asyncMap<T, U>(array: ReadonlyArray<T>,
callbackfn: (value: T, index: number, array: ReadonlyArray<T>) => Promise<U> | U, thisArg?: any): Promise<U[]> {
return Promise.all(array.map(callbackfn, thisArg));
}

Просмотреть файл

@ -13,7 +13,7 @@ export class BidirectionalMap<Left, Right> {
private readonly _rightToLeft = new ValidatedMap<Right, Left>();
constructor(initialContents?: Iterable<[Left, Right]> | ReadonlyArray<[Left, Right]>) {
this._leftToRight = new ValidatedMap<Left, Right>(initialContents);
this._leftToRight = initialContents ? new ValidatedMap<Left, Right>(initialContents) : new ValidatedMap<Left, Right>();
const reversed = Array.from(this._leftToRight.entries()).map(e => <[Right, Left]>[e[1], e[0]]);
this._rightToLeft = new ValidatedMap<Right, Left>(reversed);
}

Просмотреть файл

@ -0,0 +1,29 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { ValidatedMultiMap } from './validatedMultiMap';
export function groupByKey<T, K>(elements: T[], obtainKey: (element: T) => K): ValidatedMultiMap<K, T> {
const grouped = ValidatedMultiMap.empty<K, T>();
elements.forEach(element => grouped.add(obtainKey(element), element));
return grouped;
}
export function determineOrderingOfStrings(left: string, right: string): number {
if (left < right) {
return -1;
} else if (left > right) {
return 1;
} else {
return 0;
}
}
export function singleElementOfArray<T>(array: ReadonlyArray<T>): T {
if (array.length === 1) {
return array[0];
} else {
throw new Error(`Expected array ${array} to have exactly a single element yet it had ${array.length}`);
}
}

Просмотреть файл

@ -0,0 +1,130 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { ValidatedMap, IValidatedMap } from './validatedMap';
import { printMap } from './printing';
import { ValidatedSet, IValidatedSet } from './validatedSet';
/** A multi map that throws exceptions instead of returning error codes. */
export class ValidatedMultiMap<K, V> {
public get keysSize(): number {
return this._wrappedMap.size;
}
public get [Symbol.toStringTag](): 'Map' {
return 'ValidatedMultiMap' as 'Map';
}
private constructor(private readonly _wrappedMap: IValidatedMap<K, IValidatedSet<V>>) { }
public static empty<K, V>(): ValidatedMultiMap<K, V> {
return this.usingCustomMap(new ValidatedMap<K, IValidatedSet<V>>());
}
public static withContents<K, V>(initialContents: Map<K, Set<V>> | Iterable<[K, Set<V>]> | ReadonlyArray<[K, Set<V>]>): ValidatedMultiMap<K, V> {
const elements = Array.from(initialContents).map(element => <[K, IValidatedSet<V>]>[element[0], new ValidatedSet(element[1])]);
return this.usingCustomMap(new ValidatedMap<K, IValidatedSet<V>>(elements));
}
public static usingCustomMap<K, V>(wrappedMap: IValidatedMap<K, IValidatedSet<V>>): ValidatedMultiMap<K, V> {
return new ValidatedMultiMap(wrappedMap);
}
public clear(): void {
this._wrappedMap.clear();
}
public delete(key: K): boolean {
return this._wrappedMap.delete(key);
}
public forEach(callbackfn: (value: Set<V>, key: K, map: Map<K, Set<V>>) => void, thisArg?: any): void {
this._wrappedMap.forEach(callbackfn, thisArg);
}
public get(key: K): Set<V> {
return this._wrappedMap.get(key);
}
public getOr(key: K, elementDoesntExistAction: () => Set<V>): Set<V> {
return this._wrappedMap.getOr(key, () => new ValidatedSet(elementDoesntExistAction()));
}
public has(key: K): boolean {
return this._wrappedMap.has(key);
}
public addKeyIfNotExistant(key: K): this {
const existingValues = this._wrappedMap.tryGetting(key);
if (existingValues === undefined) {
this._wrappedMap.set(key, new ValidatedSet());
}
return this;
}
public add(key: K, value: V): this {
const existingValues = this._wrappedMap.tryGetting(key);
if (existingValues !== undefined) {
existingValues.add(value);
} else {
this._wrappedMap.set(key, new ValidatedSet([value]));
}
return this;
}
public addAndIgnoreDuplicates(key: K, value: V): this {
const existingValues = this._wrappedMap.tryGetting(key);
if (existingValues !== undefined) {
existingValues.addOrReplaceIfExists(value);
} else {
this._wrappedMap.set(key, new ValidatedSet([value]));
}
return this;
}
public removeValueAndIfLastRemoveKey(key: K, value: V): this {
const remainingValues = this.removeValue(key, value);
if (remainingValues.size === 0) {
this._wrappedMap.delete(key);
}
return this;
}
public removeValue(key: K, value: V): Set<V> {
const existingValues = this._wrappedMap.get(key);
if (!existingValues.delete(value)) {
throw new Error(`Failed to delete the value ${value} under key ${key} because it wasn't present`);
}
return existingValues;
}
[Symbol.iterator](): IterableIterator<[K, Set<V>]> {
return this._wrappedMap.entries();
}
public entries(): IterableIterator<[K, Set<V>]> {
return this._wrappedMap.entries();
}
public keys(): IterableIterator<K> {
return this._wrappedMap.keys();
}
public values(): IterableIterator<Set<V>> {
return this._wrappedMap.values();
}
public tryGetting(key: K): Set<V> | undefined {
return this._wrappedMap.tryGetting(key);
}
public toString(): string {
return printMap('ValidatedMultiMap', this);
}
}

Просмотреть файл

@ -15,7 +15,9 @@ export class ValidatedSet<K> implements IValidatedSet<K> {
public constructor(iterable: Iterable<K>);
public constructor(values?: ReadonlyArray<K>);
public constructor(valuesOrIterable?: ReadonlyArray<K> | undefined | Iterable<K>) {
this._wrappedSet = new Set(valuesOrIterable);
this._wrappedSet = valuesOrIterable
? new Set(valuesOrIterable)
: new Set();
}
public get size(): number {

Просмотреть файл

@ -2,6 +2,7 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as _ from 'lodash';
import * as path from 'path';
import { printTopLevelObjectDescription } from './printObjectDescription';
import { logger } from 'vscode-debugadapter';
@ -23,19 +24,24 @@ export class ReplacementInstruction {
export interface IMethodsCalledLoggerConfiguration {
readonly replacements: ReplacementInstruction[];
decideWhetherToWrapMethodResult(methodName: string | symbol | number, args: unknown[], result: unknown, wrapWithName: (name: string) => void): void;
decideWhetherToWrapEventEmitterListener(receiverName: string, methodName: string | symbol | number, args: unknown[], wrapWithName: (name: string) => void): void;
customizeResult(methodName: string | symbol | number, args: unknown[], result: unknown): unknown;
customizeArgumentsBeforeCall(receiverName: string, methodName: string | symbol | number, args: unknown[]): void;
}
export class MethodsCalledLoggerConfiguration implements IMethodsCalledLoggerConfiguration {
public decideWhetherToWrapMethodResult(_methodName: string | symbol | number, _args: unknown[], _result: unknown, _wrapWithName: (name: string) => void): void { }
public decideWhetherToWrapEventEmitterListener(receiverName: string, methodName: string | symbol | number, args: unknown[], wrapWithName: (name: string) => void): void {
if (methodName === 'on') {
wrapWithName(`(${receiverName} emits ${args[0]})`);
}
public constructor(public readonly containerName: string, private _replacements: ReplacementInstruction[]) { }
public customizeResult(_methodName: string | symbol | number, _args: unknown[], result: unknown): unknown {
return result;
}
public constructor(private _replacements: ReplacementInstruction[]) { }
public customizeArgumentsBeforeCall(receiverName: string, methodName: string | symbol | number, args: object[]): void {
if (methodName === 'on' && args.length >= 2) {
args[1] = new MethodsCalledLogger(this, args[1], `(${receiverName} emits ${args[0]})`).wrapped();
}
}
public get replacements(): ReplacementInstruction[] {
return this._replacements;
@ -47,6 +53,7 @@ export class MethodsCalledLoggerConfiguration implements IMethodsCalledLoggerCon
}
export class MethodsCalledLogger<T extends object> {
private static _nextCallId = 10000;
constructor(
private readonly _configuration: IMethodsCalledLoggerConfiguration,
private readonly _objectToWrap: T,
@ -55,36 +62,38 @@ export class MethodsCalledLogger<T extends object> {
public wrapped(): T {
const handler = {
get: <K extends keyof T>(target: T, propertyKey: K, _receiver: any) => {
get: <K extends keyof T>(target: T, propertyKey: K, receiver: any) => {
const originalPropertyValue = target[propertyKey];
if (typeof originalPropertyValue === 'function') {
return (...args: any) => {
const callId = this.generateCallId();
try {
if (propertyKey === 'on' && args.length >= 2) {
let listenerPossiblyWrapped = args[1];
this._configuration.decideWhetherToWrapEventEmitterListener(this._objectToWrapName, propertyKey, args, name => listenerPossiblyWrapped = new MethodsCalledLogger(this._configuration, args[1], name).wrapped());
args[1] = listenerPossiblyWrapped;
}
this.logCallStart(propertyKey, args, callId);
this._configuration.customizeArgumentsBeforeCall(this._objectToWrapName, propertyKey, args);
const result = originalPropertyValue.apply(target, args);
if (!result || !result.then) {
this.logCall(propertyKey, Synchronicity.Sync, args, Outcome.Succesful, result);
let resultPossiblyWrapped = result;
this._configuration.decideWhetherToWrapMethodResult(propertyKey, args, result, name => resultPossiblyWrapped = new MethodsCalledLogger(this._configuration, result, name).wrapped());
return resultPossiblyWrapped;
this.logCall(propertyKey, Synchronicity.Sync, args, Outcome.Succesful, result, callId);
if (result === target) {
return receiver;
} else {
return this._configuration.customizeResult(propertyKey, args, result);
}
} else {
this.logSyncPartFinished(propertyKey, args, callId);
return result.then((promiseResult: unknown) => {
this.logCall(propertyKey, Synchronicity.Async, args, Outcome.Succesful, promiseResult);
let resultPossiblyWrapped = promiseResult;
this._configuration.decideWhetherToWrapMethodResult(propertyKey, args, promiseResult, name => resultPossiblyWrapped = new MethodsCalledLogger(this._configuration, <object>promiseResult, name).wrapped());
return resultPossiblyWrapped;
this.logCall(propertyKey, Synchronicity.Async, args, Outcome.Succesful, promiseResult, callId);
if (promiseResult === target) {
return receiver;
} else {
return this._configuration.customizeResult(propertyKey, args, promiseResult);
}
}, (error: unknown) => {
this.logCall(propertyKey, Synchronicity.Async, args, Outcome.Failure, error);
this.logCall(propertyKey, Synchronicity.Async, args, Outcome.Failure, error, callId);
return Promise.reject(error);
});
}
} catch (exception) {
this.logCall(propertyKey, Synchronicity.Sync, args, Outcome.Failure, exception);
this.logCall(propertyKey, Synchronicity.Sync, args, Outcome.Failure, exception, callId);
throw exception;
}
};
@ -97,6 +106,10 @@ export class MethodsCalledLogger<T extends object> {
return new Proxy<T>(this._objectToWrap, handler);
}
private generateCallId(): number {
return MethodsCalledLogger._nextCallId++;
}
private printMethodCall(propertyKey: PropertyKey, methodCallArguments: any[]): string {
return `${this._objectToWrapName}.${String(propertyKey)}(${this.printArguments(methodCallArguments)})`;
}
@ -109,8 +122,43 @@ export class MethodsCalledLogger<T extends object> {
return `${synchronicity === Synchronicity.Sync ? '' : ' async'}`;
}
private logCall(propertyKey: PropertyKey, synchronicity: Synchronicity, methodCallArguments: any[], outcome: Outcome, resultOrException: unknown): void {
const message = `${this.printMethodCall(propertyKey, methodCallArguments)} ${this.printMethodSynchronicity(synchronicity)} ${this.printMethodResponse(outcome, resultOrException)}`;
/** Returns the test file and line that the code is currently executing e.g.:
* < >
* [22:23:28.468 UTC] START 10026: hitCountBreakpointTests.test.ts:34:2 | #incrementBtn.click()
*/
// TODO: Figure out how to integrate this with V2. We don't want to do this for production logging because new Error().stack is slow
private getTestFileAndLine(): string {
const stack = new Error().stack;
if (stack) {
const stackLines = stack.split('\n');
const testCaseLine = stackLines.find(line => line.indexOf('test.ts') >= 0);
if (testCaseLine) {
const filenameAndLine = testCaseLine.lastIndexOf(path.sep);
if (filenameAndLine >= 0) {
const fileNameAndLineNumber = testCaseLine.substring(filenameAndLine + 1, testCaseLine.length - 2);
return `${fileNameAndLineNumber} | `;
}
}
}
return '';
}
private logCallStart(propertyKey: PropertyKey, methodCallArguments: any[], callId: number): void {
const getTestFileAndLine = this.getTestFileAndLine();
const message = `START ${callId}: ${getTestFileAndLine}${this.printMethodCall(propertyKey, methodCallArguments)}`;
logger.verbose(message);
}
private logSyncPartFinished(propertyKey: PropertyKey, methodCallArguments: any[], callId: number): void {
const getTestFileAndLine = this.getTestFileAndLine();
const message = `PROMISE-RETURNED ${callId}: ${getTestFileAndLine}${this.printMethodCall(propertyKey, methodCallArguments)}`;
logger.verbose(message);
}
private logCall(propertyKey: PropertyKey, synchronicity: Synchronicity, methodCallArguments: any[], outcome: Outcome, resultOrException: unknown, callId: number): void {
const endPrefix = callId ? `END ${callId}: ` : '';
const message = `${endPrefix}${this.printMethodCall(propertyKey, methodCallArguments)} ${this.printMethodSynchronicity(synchronicity)} ${this.printMethodResponse(outcome, resultOrException)}`;
logger.verbose(message);
}
@ -130,5 +178,5 @@ export class MethodsCalledLogger<T extends object> {
}
export function wrapWithMethodLogger<T extends object>(objectToWrap: T, objectToWrapName = `${objectToWrap}`): T {
return new MethodsCalledLogger(new MethodsCalledLoggerConfiguration([]), objectToWrap, objectToWrapName).wrapped();
return new MethodsCalledLogger(new MethodsCalledLoggerConfiguration('no container', []), objectToWrap, objectToWrapName).wrapped();
}

Просмотреть файл

@ -14,16 +14,23 @@ export function printObjectDescription(objectToPrint: unknown, fallbackPrintDesc
// This is a noice-json-rpc proxy
printed = 'CDTP Proxy';
} else {
const toString = objectToPrint.toString();
if (toString !== '[object Object]') {
printed = toString;
} else if (isJSONObject(objectToPrint)) {
printed = JSON.stringify(objectToPrint);
} else if (objectToPrint.constructor === Object) {
printed = fallbackPrintDescription(objectToPrint);
// This if is actually unnecesary, the previous if (!objectToPrint) { does the same thing. For some reason the typescript compiler cannot infer the type from that if
// so we just write this code to leave the compiler happy
// TODO: Sync with the typescript team and figure out how to remove this
if (!objectToPrint) {
printed = `${objectToPrint}`;
} else {
printed = `${objectToPrint}(${objectToPrint.constructor.name})`;
}
const toString = objectToPrint.toString();
if (toString !== '[object Object]') {
printed = toString;
} else if (isJSONObject(objectToPrint)) {
printed = JSON.stringify(objectToPrint);
} else if (objectToPrint.constructor === Object) {
printed = fallbackPrintDescription(objectToPrint);
} else {
printed = `${objectToPrint}(${objectToPrint.constructor.name})`;
}
}
}
} else if (typeof objectToPrint === 'function') {
if (objectToPrint.name) {
@ -46,7 +53,7 @@ export function printObjectDescription(objectToPrint: unknown, fallbackPrintDesc
function isJSONObject(objectToPrint: any): boolean {
if (objectToPrint.constructor === Object) {
const values = _.values(objectToPrint);
return values.every(value => value.constructor === Object);
return values.every(value => !value || value.constructor === Object);
} else {
return false;
}

Просмотреть файл

@ -16,3 +16,7 @@ export function isNotUndefined<T>(object: T | undefined): object is T {
export interface Array<T> {
filter<U extends T>(predicate: (element: T) => element is U): U[];
}
export type Replace<T, R extends keyof T, N> = {
[K in keyof T]: K extends R ? N : T[K];
};

Просмотреть файл

@ -15,23 +15,6 @@ import { BreakpointsWizard as BreakpointsWizard } from '../wizards/breakpoints/b
import { asyncRepeatSerially } from '../utils/repeat';
puppeteerSuite('Hit count breakpoints on a React project', reactTestSpecification, (suiteContext) => {
const reactCounterAppBaseStack = `
ca [react-dom.production.min.js] Line 49:1
ja [react-dom.production.min.js] Line 69:1
ka [react-dom.production.min.js] Line 73:1
wa [react-dom.production.min.js] Line 140:1
Aa [react-dom.production.min.js] Line 169:6
ya [react-dom.production.min.js] Line 158:1
Da [react-dom.production.min.js] Line 232:1
Ad [react-dom.production.min.js] Line 1718:1
Gi [react-dom.production.min.js] Line 5990:1
Kb [react-dom.production.min.js] Line 660:1
Dd [react-dom.production.min.js] Line 1760:1
(anonymous function) [react-dom.production.min.js] Line 6017:1
push../node_modules/scheduler/cjs/scheduler.production.min.js.exports.unstable_runWithPriority [scheduler.production.min.js] Line 274:1
Ii [react-dom.production.min.js] Line 6016:1
Cd [react-dom.production.min.js] Line 1737:1`;
puppeteerTest("Hit count breakpoint = 3 pauses on the button's 3rd click", suiteContext, async (_context, page) => {
const incBtn = await page.waitForSelector('#incrementBtn');
@ -39,20 +22,18 @@ puppeteerSuite('Hit count breakpoints on a React project', reactTestSpecificatio
const counterBreakpoints = breakpoints.at('Counter.jsx');
const setStateBreakpoint = await counterBreakpoints.hitCountBreakpoint({
lineText: 'this.setState({ count: newval });',
text: 'this.setState({ count: newval });',
boundText: 'setState({ count: newval })',
hitCountCondition: '% 3'
});
await asyncRepeatSerially(2, () => incBtn.click());
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
increment [Counter.jsx] Line 17:12
onClick [Counter.jsx] Line 30:60
${reactCounterAppBaseStack}`);
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
await incBtn.click();
await breakpoints.assertNotPaused();
await breakpoints.waitAndAssertNoMoreEvents();
await setStateBreakpoint.unset();
});
@ -64,40 +45,32 @@ puppeteerSuite('Hit count breakpoints on a React project', reactTestSpecificatio
const counterBreakpoints = breakpoints.at('Counter.jsx');
const setStateBreakpoint = await counterBreakpoints.hitCountBreakpoint({
lineText: 'this.setState({ count: newval });',
text: 'this.setState({ count: newval })',
boundText: 'setState({ count: newval })',
hitCountCondition: '= 3'
});
const setNewValBreakpoint = await counterBreakpoints.hitCountBreakpoint({
lineText: 'const newval = this.state.count + 1',
text: 'const newval = this.state.count + 1',
boundText: 'state.count + 1',
hitCountCondition: '= 5'
});
const stepInBreakpoint = await counterBreakpoints.hitCountBreakpoint({
lineText: 'this.stepIn();',
text: 'this.stepIn()',
boundText: 'stepIn()',
hitCountCondition: '= 4'
});
await asyncRepeatSerially(2, () => incBtn.click());
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
increment [Counter.jsx] Line 17:12
onClick [Counter.jsx] Line 30:60
${reactCounterAppBaseStack}`);
await stepInBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
increment [Counter.jsx] Line 18:12
onClick [Counter.jsx] Line 30:60
${reactCounterAppBaseStack}`);
await setNewValBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
increment [Counter.jsx] Line 16:27
onClick [Counter.jsx] Line 30:60
${reactCounterAppBaseStack}`);
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
await stepInBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
await setNewValBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
await incBtn.click();
await breakpoints.assertNotPaused();
await breakpoints.waitAndAssertNoMoreEvents();
await setStateBreakpoint.unset();
await setNewValBreakpoint.unset();
@ -112,41 +85,33 @@ puppeteerSuite('Hit count breakpoints on a React project', reactTestSpecificatio
const { setStateBreakpoint, stepInBreakpoint, setNewValBreakpoint } = await counterBreakpoints.batch(async () => ({
setStateBreakpoint: await counterBreakpoints.hitCountBreakpoint({
lineText: 'this.setState({ count: newval });',
text: 'this.setState({ count: newval });',
boundText: 'setState({ count: newval })',
hitCountCondition: '= 3'
}),
setNewValBreakpoint: await counterBreakpoints.hitCountBreakpoint({
lineText: 'const newval = this.state.count + 1',
text: 'const newval = this.state.count + 1',
boundText: 'state.count + 1',
hitCountCondition: '= 5'
}),
stepInBreakpoint: await counterBreakpoints.hitCountBreakpoint({
lineText: 'this.stepIn();',
text: 'this.stepIn();',
boundText: 'stepIn()',
hitCountCondition: '= 4'
})
}));
await asyncRepeatSerially(2, () => incBtn.click());
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
increment [Counter.jsx] Line 17:12
onClick [Counter.jsx] Line 30:60
${reactCounterAppBaseStack}`);
await stepInBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
increment [Counter.jsx] Line 18:12
onClick [Counter.jsx] Line 30:60
${reactCounterAppBaseStack}`);
await setNewValBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
increment [Counter.jsx] Line 16:27
onClick [Counter.jsx] Line 30:60
${reactCounterAppBaseStack}`);
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
await stepInBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
await setNewValBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
await incBtn.click();
await breakpoints.assertNotPaused();
await breakpoints.waitAndAssertNoMoreEvents();
await counterBreakpoints.batch(async () => {
await setStateBreakpoint.unset();

Просмотреть файл

@ -0,0 +1,126 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
* Hit count breakpoints' scenarios
* Hit count breakpoint syntax: (>|>=|=|<|<=|%)?\s*([0-9]+)
*/
import * as _ from 'lodash';
import { puppeteerSuite, puppeteerTest } from '../puppeteer/puppeteerSuite';
import { reactWithLoopTestSpecification } from '../resources/resourceProjects';
import { BreakpointsWizard as BreakpointsWizard } from '../wizards/breakpoints/breakpointsWizard';
import { expect } from 'chai';
import { logger } from 'vscode-debugadapter';
puppeteerSuite('Hit count breakpoints combinations', reactWithLoopTestSpecification, (suiteContext) => {
interface IConditionConfiguration {
condition: string; // The condition for the hit count breakpoint
iterationsExpectedToPause: number[]; // In which iteration numbers it should pause (e.g.: 1st, 5th, 12th, etc...)
noMorePausesAfterwards: boolean;
}
// * Hit count breakpoint syntax: (>|>=|=|<|<=|%)?\s*([0-9]+)
const manyConditionsConfigurations: IConditionConfiguration[] = [
{ condition: '= 0', iterationsExpectedToPause: [], noMorePausesAfterwards: true },
{ condition: '= 1', iterationsExpectedToPause: [1], noMorePausesAfterwards: true },
{ condition: '= 2', iterationsExpectedToPause: [2], noMorePausesAfterwards: true },
{ condition: '= 12', iterationsExpectedToPause: [12], noMorePausesAfterwards: true },
{ condition: '> 0', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
{ condition: '> 1', iterationsExpectedToPause: [2, 3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
{ condition: '>\t2', iterationsExpectedToPause: [3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
{ condition: '> 187', iterationsExpectedToPause: [188, 189, 190, 191], noMorePausesAfterwards: false },
{ condition: '>= 0', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
{ condition: '>= 1', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
{ condition: '>= 2', iterationsExpectedToPause: [2, 3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
{ condition: '>= 37', iterationsExpectedToPause: [37, 38, 39], noMorePausesAfterwards: false },
{ condition: '< 0', iterationsExpectedToPause: [], noMorePausesAfterwards: true },
{ condition: '< \t \t 1', iterationsExpectedToPause: [], noMorePausesAfterwards: true },
{ condition: '< 2', iterationsExpectedToPause: [1], noMorePausesAfterwards: true },
{ condition: '< \t13', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], noMorePausesAfterwards: true },
{ condition: '<=\t 0', iterationsExpectedToPause: [], noMorePausesAfterwards: true },
{ condition: '<= 1', iterationsExpectedToPause: [1], noMorePausesAfterwards: true },
{ condition: '<= 15', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], noMorePausesAfterwards: true },
{ condition: '% 0', iterationsExpectedToPause: [], noMorePausesAfterwards: true },
{ condition: '% 1', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6], noMorePausesAfterwards: false },
{ condition: '% 2', iterationsExpectedToPause: [2, 4, 6, 8, 10], noMorePausesAfterwards: false },
{ condition: '%\t3', iterationsExpectedToPause: [3, 6, 9, 12, 15], noMorePausesAfterwards: false },
{ condition: '% \t \t \t 12', iterationsExpectedToPause: [12, 24, 36, 48, 60], noMorePausesAfterwards: false },
{ condition: '%\t\t\t17', iterationsExpectedToPause: [17, 34, 51, 68], noMorePausesAfterwards: false },
{ condition: '% 37', iterationsExpectedToPause: [37, 74, 111, 148], noMorePausesAfterwards: false },
];
manyConditionsConfigurations.forEach(conditionConfiguration => {
puppeteerTest(`condition ${conditionConfiguration.condition}`, suiteContext, async (_context, page) => {
const incBtn = await page.waitForSelector('#incrementBtn');
const breakpoints = BreakpointsWizard.create(suiteContext.debugClient, reactWithLoopTestSpecification);
const counterBreakpoints = breakpoints.at('Counter.jsx');
const setStateBreakpoint = await counterBreakpoints.hitCountBreakpoint({
text: 'iterationNumber * iterationNumber',
hitCountCondition: conditionConfiguration.condition
});
const buttonClicked = incBtn.click();
for (const nextIterationToPause of conditionConfiguration.iterationsExpectedToPause) {
/**
* The iterationNumber variable counts in the js-debuggee code how many times the loop was executed. We verify
* the value of this variable to validate that a bp with = 12 paused on the 12th iteration rather than on the 1st one
* (The breakpoint is located in the same place in both iterations, so we need to use state to differenciate between those two cases)
*/
await setStateBreakpoint.assertIsHitThenResume({ variables: { local_contains: { iterationNumber: nextIterationToPause } } });
}
logger.log(`No more pauses afterwards = ${conditionConfiguration.noMorePausesAfterwards}`);
if (conditionConfiguration.noMorePausesAfterwards) {
await breakpoints.waitAndAssertNoMoreEvents();
await setStateBreakpoint.unset();
} else {
await breakpoints.waitAndConsumePausedEvent(setStateBreakpoint);
await setStateBreakpoint.unset();
await breakpoints.resume();
}
await buttonClicked;
});
});
// * Hit count breakpoint syntax: (>|>=|=|<|<=|%)?\s*([0-9]+)
const manyInvalidConditions: string[] = [
'== 3',
'= -1',
'> -200',
'< -24',
'< 64\t',
'< 5 ',
'>= -95',
'<= -5',
'\t= 1',
'< = 4',
' <= 4',
'% -200',
'stop always',
' = 3 ',
'= 1 + 1',
'> 3.5',
];
manyInvalidConditions.forEach(invalidCondition => {
puppeteerTest(`invalid condition ${invalidCondition}`, suiteContext, async () => {
const breakpoints = BreakpointsWizard.create(suiteContext.debugClient, reactWithLoopTestSpecification);
const counterBreakpoints = breakpoints.at('Counter.jsx');
try {
await counterBreakpoints.hitCountBreakpoint({
text: 'iterationNumber * iterationNumber',
hitCountCondition: invalidCondition
});
} catch (exception) {
expect(exception.toString()).to.be.equal(`Error: [debugger-for-chrome] Error processing "setBreakpoints": Didn't recognize <${invalidCondition}> as a valid hit count condition`);
}
});
});
});

Просмотреть файл

@ -0,0 +1,25 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as testSetup from '../testSetup';
import { puppeteerSuite } from '../puppeteer/puppeteerSuite';
import { TestProjectSpec } from '../framework/frameworkTestSupport';
import { FrameworkTestSuite } from '../framework/frameworkCommonTests';
import * as path from 'path';
import { utils } from 'vscode-chrome-debug-core';
const SINGLE_INLINE_TEST_SPEC = TestProjectSpec.fromTestPath('inline_scripts', '', utils.pathToFileURL(path.join(testSetup.DATA_ROOT, 'inline_scripts/single.html')));
const MULTIPLE_INLINE_TEST_SPEC = TestProjectSpec.fromTestPath('inline_scripts', '', utils.pathToFileURL(path.join(testSetup.DATA_ROOT, 'inline_scripts/multiple.html')));
suite('Inline Script Tests', () => {
puppeteerSuite('Single inline script', SINGLE_INLINE_TEST_SPEC, (suiteContext) => {
const frameworkTests = new FrameworkTestSuite('Simple JS', suiteContext);
frameworkTests.testBreakpointHitsOnPageAction('Should stop on a breakpoint in an in-line script', '#actionButton', 'single.html', 'a + b;', page => page.click('#actionButton') );
});
puppeteerSuite.skip('Multiple inline scripts', MULTIPLE_INLINE_TEST_SPEC, (suiteContext) => {
const frameworkTests = new FrameworkTestSuite('Simple JS', suiteContext);
frameworkTests.testBreakpointHitsOnPageAction('Should stop on a breakpoint in multiple in-line scripts (Skipped, not currently working in V2)',
'#actionButton', 'multiple.html', 'inlineScript1', page => page.click('#actionButton') );
});
});

Просмотреть файл

@ -0,0 +1,289 @@
import { TestProjectSpec } from '../framework/frameworkTestSupport';
import { VariablesWizard } from '../wizards/variables/variablesWizard';
import { LaunchProject } from '../fixtures/launchProject';
import { testUsing } from '../fixtures/testUsing';
// Scopes' kinds: 'global' | 'local' | 'with' | 'closure' | 'catch' | 'block' | 'script' | 'eval' | 'module'
// TODO: Test several scopes at the same time. They can be repeated, and the order does matter
suite('Variables scopes', function () {
testUsing('local', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/localScope')), async (launchProject) => {
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
local: `
this = Window (Object)
arguments = Arguments(0) [] (Object)
b = body {text: "", link: "", vLink: "", } (Object)
bool = true (boolean)
buffer = ArrayBuffer(8) {} (Object)
buffView = Int32Array(2) [234, 0] (Object)
consoleDotLog = function consoleDotLog(m) { } (Function)
e = Error: hi (Object)
element = body {text: "", link: "", vLink: "", } (Object)
fn = () => { } (Function)
fn2 = function () { } (Function)
globalCode = "page loaded" (string)
inf = Infinity (number)
infStr = "Infinity" (string)
longStr = "this is a\nstring with\nnewlines" (string)
m = Map(1) {} (Object)
manyPropsObj = Object {0: 1, 1: 3, 2: 5, } (Object)
myVar = Object {num: 1, str: "Global", obj: Object, } (Object)
nan = NaN (number)
obj = Object {a: 2, thing: <accessor>} (Object)
qqq = undefined (undefined)
r = /^asdf.*$/g {lastIndex: 0} (Object)
s = Symbol(hi) (symbol)
str = "hello" (string)
xyz = 4 (number)`}
);
});
testUsing('globals', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/globalScope')), async (launchProject) => {
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
await new VariablesWizard(launchProject.debugClient).assertNewGlobalVariariablesAre(async () => {
await launchProject.pausedWizard.resume();
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
},
// The variables declared with const, and let aren't global variables so they won't appear here
`
b = body {text: "", link: "", vLink: "", } (Object)
bool = true (boolean)
buffer = ArrayBuffer(8) {} (Object)
buffView = Int32Array(2) [234, 0] (Object)
consoleDotLog = function consoleDotLog(m) { } (Function)
e = Error: hi (Object)
element = p {align: "", title: "", lang: "", } (Object)
evalVar1 = 16 (number)
evalVar2 = "sdlfk" (string)
evalVar3 = Array(3) [1, 2, 3] (Object)
fn = () => { } (Function)
fn2 = function () { } (Function)
globalCode = "page loaded" (string)
i = 101 (number)
inf = Infinity (number)
infStr = "Infinity" (string)
longStr = "this is a\nstring with\nnewlines" (string)
m = Map(1) {} (Object)
manyPropsObj = Object {0: 1, 1: 3, 2: 5, } (Object)
myVar = Object {num: 1, str: "Global", obj: Object, } (Object)
nan = NaN (number)
obj = Object {a: 2, thing: <accessor>} (Object)
qqq = undefined (undefined)
r = /^asdf.*$/g {lastIndex: 0} (Object) // TODO: This and other types seems wrong. Investigate
s = Symbol(hi) (symbol)
str = "hello" (string)
xyz = 4 (number)`);
});
testUsing('script', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/scriptScope')), async (launchProject) => {
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
script: `
this = Window (Object)
b = body {text: "", link: "", vLink: "", } (Object)
bool = true (boolean)
buffer = ArrayBuffer(8) {} (Object)
buffView = Int32Array(2) [234, 0] (Object)
e = Error: hi (Object)
element = body {text: "", link: "", vLink: "", } (Object)
fn = () => { } (Function)
fn2 = function () { } (Function)
globalCode = "page loaded" (string)
inf = Infinity (number)
infStr = "Infinity" (string)
longStr = "this is a\nstring with\nnewlines" (string)
m = Map(1) {} (Object)
manyPropsObj = Object {0: 1, 1: 3, 2: 5, } (Object)
myVar = Object {num: 1, str: "Global", obj: Object, } (Object)
nan = NaN (number)
obj = Object {a: 2, thing: <accessor>} (Object)
qqq = undefined (undefined)
r = /^asdf.*$/g {lastIndex: 0} (Object)
s = Symbol(hi) (symbol)
str = "hello" (string)
xyz = 4 (number)`}
);
});
testUsing('block', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/blockScope')), async (launchProject) => {
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre(
{
block: `
this = Window (Object)
b = body {text: "", link: "", vLink: "", } (Object)
bool = true (boolean)
buffer = ArrayBuffer(8) {} (Object)
buffView = Int32Array(2) [234, 0] (Object)
consoleDotLog = function consoleDotLog(m) { } (Function)
e = Error: hi (Object)
element = body {text: "", link: "", vLink: "", } (Object)
fn = () => { } (Function)
fn2 = function () { } (Function)
globalCode = "page loaded" (string)
inf = Infinity (number)
infStr = "Infinity" (string)
longStr = "this is a\nstring with\nnewlines" (string)
m = Map(1) {} (Object)
manyPropsObj = Object {0: 1, 1: 3, 2: 5, } (Object)
myVar = Object {num: 1, str: "Global", obj: Object, } (Object)
nan = NaN (number)
obj = Object {a: 2, thing: <accessor>} (Object)
qqq = undefined (undefined)
r = /^asdf.*$/g {lastIndex: 0} (Object)
s = Symbol(hi) (symbol)
str = "hello" (string)
xyz = 4 (number)`
}
);
});
testUsing('catch', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/catchScope')), async (launchProject) => {
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
catch: `
exception = Error: Something went wrong (Object)`}
);
});
testUsing('closure', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/closureScope')), async (launchProject) => {
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
closure: `
arguments = Arguments(0) [] (Object)
b = body {text: "", link: "", vLink: "", } (Object)
bool = true (boolean)
buffer = ArrayBuffer(8) {} (Object)
buffView = Int32Array(2) [234, 0] (Object)
consoleDotLog = function consoleDotLog(m) { } (Function)
e = Error: hi (Object)
element = body {text: "", link: "", vLink: "", } (Object)
fn = () => { } (Function)
fn2 = function () { } (Function)
globalCode = "page loaded" (string)
inf = Infinity (number)
infStr = "Infinity" (string)
longStr = "this is a\nstring with\nnewlines" (string)
m = Map(1) {} (Object)
manyPropsObj = Object {0: 1, 1: 3, 2: 5, } (Object)
myVar = Object {num: 1, str: "Global", obj: Object, } (Object)
nan = NaN (number)
obj = Object {a: 2, thing: <accessor>} (Object)
pauseInside = function pauseInside() { } (Function)
qqq = undefined (undefined)
r = /^asdf.*$/g {lastIndex: 0} (Object)
s = Symbol(hi) (symbol)
str = "hello" (string)
xyz = 4 (number)`}
);
});
testUsing('eval', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/evalScope')), async (launchProject) => {
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
eval: `
this = Window (Object)
b = body {text: "", link: "", vLink: "", } (Object)
bool = true (boolean)
buffer = ArrayBuffer(8) {} (Object)
buffView = Int32Array(2) [234, 0] (Object)
e = Error: hi (Object)
element = body {text: "", link: "", vLink: "", } (Object)
fn = () => { } (Function)
fn2 = function () { } (Function)
globalCode = "page loaded" (string)
inf = Infinity (number)
infStr = "Infinity" (string)
longStr = "this is a\nstring with\nnewlines" (string)
m = Map(1) {} (Object)
manyPropsObj = Object {0: 1, 1: 3, 2: 5, } (Object)
myVar = Object {num: 1, str: "Global", obj: Object, } (Object)
nan = NaN (number)
obj = Object {a: 2, thing: <accessor>} (Object)
qqq = undefined (undefined)
r = /^asdf.*$/g {lastIndex: 0} (Object)
s = Symbol(hi) (symbol)
str = "hello" (string)
xyz = 4 (number)`}
);
});
testUsing('with', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/withScope')), async (launchProject) => {
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
with: `
this = Window (Object)
b = body {text: "", link: "", vLink: "", } (Object)
bool = true (boolean)
buffer = ArrayBuffer(8) {} (Object)
buffView = Int32Array(2) [234, 0] (Object)
consoleDotLog = function (m) { } (Function)
e = Error: hi (Object)
element = body {text: "", link: "", vLink: "", } (Object)
evalVar1 = 16 (number)
evalVar2 = "sdlfk" (string)
evalVar3 = Array(3) [1, 2, 3] (Object)
fn = () => { } (Function)
fn2 = function () { } (Function)
globalCode = "page loaded" (string)
i = 101 (number)
inf = Infinity (number)
infStr = "Infinity" (string)
longStr = "this is a
string with
newlines" (string)
m = Map(1) {} (Object)
manyPropsObj = Object {0: 1, 1: 3, 2: 5, } (Object)
myVar = Object {num: 1, str: "Global", obj: Object, } (Object)
nan = NaN (number)
obj = Object {a: 2, thing: <accessor>} (Object)
r = /^asdf.*$/g {lastIndex: 0} (Object)
s = Symbol(hi) (symbol)
str = "hello" (string)
xyz = 4 (number)
__proto__ = Object {constructor: , __defineGetter__: , __defineSetter__: , } (Object)`
});
});
testUsing('module', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/moduleScope')), async (launchProject) => {
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
module: `
this = undefined (undefined)
b = body {text: "", link: "", vLink: "", } (Object)
bool = true (boolean)
buffer = ArrayBuffer(8) {} (Object)
buffView = Int32Array(2) [234, 0] (Object)
consoleDotLog = function consoleDotLog(m2) { } (Function)
e = Error: hi (Object)
element = body {text: "", link: "", vLink: "", } (Object)
fn = () => { } (Function)
fn2 = function (param) { } (Function)
globalCode = "page loaded" (string)
inf = Infinity (number)
infStr = "Infinity" (string)
longStr = "this is a
string with
newlines" (string)
m = Map(1) {} (Object)
manyPropsObj = Object {0: 1, 1: 3, 2: 5, } (Object)
myVar = Object {num: 1, str: "Global", obj: Object, } (Object)
nan = NaN (number)
obj = Object {a: 2, thing: <accessor>} (Object)
qqq = undefined (undefined)
r = /^asdf.*$/g {lastIndex: 0} (Object)
s = Symbol(hi) (symbol)
str = "hello" (string)
xyz = 4 (number)`
});
});
});

Просмотреть файл

@ -0,0 +1,40 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { IFixture } from './fixture';
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
import * as testSetup from '../testSetup';
import { IBeforeAndAfterContext, ITestCallbackContext } from 'mocha';
import { logger } from 'vscode-debugadapter';
/**
* Default set up for all our tests. We expect all our tests to need to do this setup
* which includes configure the debug adapter, logging, etc...
*/
export class DefaultFixture implements IFixture {
private constructor(public readonly debugClient: ExtendedDebugClient) {
// Running tests on CI can time out at the default 5s, so we up this to 15s
debugClient.defaultTimeout = 15000;
}
/** Create a new fixture using the provided setup context */
public static async create(context: IBeforeAndAfterContext | ITestCallbackContext): Promise<DefaultFixture> {
return new DefaultFixture(await testSetup.setup(context));
}
/** Create a new fixture using the full title of the test case currently running */
public static async createWithTitle(testTitle: string): Promise<DefaultFixture> {
return new DefaultFixture(await testSetup.setupWithTitle(testTitle));
}
public async cleanUp(): Promise<void> {
logger.log(`Default test clean-up`);
await testSetup.teardown();
logger.log(`Default test clean-up finished`);
}
public toString(): string {
return `DefaultFixture`;
}
}

Просмотреть файл

@ -0,0 +1,27 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { PromiseOrNot } from 'vscode-chrome-debug-core';
/**
* See https://en.wikipedia.org/wiki/Test_fixture for more context
*/
/**
* A fixture represents a particular piece of set up of the context, or the environment or
* the configuration needed for a test or suite to run.
* The fixture should make those changes during it's constructor or static constructor method,
* and it'll "clean up" those changes with the cleanUp method
*/
export interface IFixture {
/** Clean-up the context, or changes made by the fixture */
cleanUp(): PromiseOrNot<void>;
}
/**
* A fixture representing that no setup is needed
*/
export class NullFixture implements IFixture {
public cleanUp(): void { }
}

Просмотреть файл

@ -0,0 +1,68 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { TestProjectSpec } from '../framework/frameworkTestSupport';
import { IFixture } from './fixture';
import { DefaultFixture } from './defaultFixture';
import { LaunchWebServer, ProvideStaticUrl } from './launchWebServer';
import { LaunchPuppeteer } from '../puppeteer/launchPuppeteer';
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
import { Page, Browser } from 'puppeteer';
import { ITestCallbackContext, IBeforeAndAfterContext } from 'mocha';
import { URL } from 'url';
import { PausedWizard } from '../wizards/pausedWizard';
/** Perform all the steps neccesary to launch a particular project such as:
* - Default fixture/setup
* - Launch web-server
* - Connect puppeteer to Chrome
*/
export class LaunchProject implements IFixture {
private constructor(
private readonly _defaultFixture: DefaultFixture,
private readonly _launchWebServer: LaunchWebServer | ProvideStaticUrl,
public readonly pausedWizard: PausedWizard,
private readonly _launchPuppeteer: LaunchPuppeteer) { }
public static async create(testContext: IBeforeAndAfterContext | ITestCallbackContext, testSpec: TestProjectSpec): Promise<LaunchProject> {
const launchWebServer = (testSpec.staticUrl) ?
new ProvideStaticUrl(new URL(testSpec.staticUrl), testSpec) :
await LaunchWebServer.launch(testSpec);
const defaultFixture = await DefaultFixture.create(testContext);
// We need to create the PausedWizard before launching the debuggee to listen to all events and avoid race conditions
const pausedWizard = PausedWizard.forClient(defaultFixture.debugClient);
const launchPuppeteer = await LaunchPuppeteer.create(defaultFixture.debugClient, launchWebServer.launchConfig);
return new LaunchProject(defaultFixture, launchWebServer, pausedWizard, launchPuppeteer);
}
/** Client for the debug adapter being used for this test */
public get debugClient(): ExtendedDebugClient {
return this._defaultFixture.debugClient;
}
/** Object to control the debugged browser via puppeteer */
public get browser(): Browser {
return this._launchPuppeteer.browser;
}
/** Object to control the debugged page via puppeteer */
public get page(): Page {
return this._launchPuppeteer.page;
}
public get url(): URL {
return this._launchWebServer.url;
}
public async cleanUp(): Promise<void> {
await this.pausedWizard.waitAndAssertNoMoreEvents();
await this._defaultFixture.cleanUp(); // Disconnect the debug-adapter first
await this._launchPuppeteer.cleanUp(); // Then disconnect puppeteer and close chrome
await this._launchWebServer.cleanUp(); // Finally disconnect the web-server
}
}

Просмотреть файл

@ -0,0 +1,82 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { createServer } from 'http-server';
import { IFixture } from './fixture';
import { TestProjectSpec } from '../framework/frameworkTestSupport';
import { HttpOrHttpsServer } from '../types/server';
import { ILaunchRequestArgs } from 'vscode-chrome-debug-core';
import { logger } from 'vscode-debugadapter';
import { URL } from 'url';
async function createServerAsync(root: string): Promise<HttpOrHttpsServer> {
const server = createServer({ root });
return await new Promise((resolve, reject) => {
logger.log(`About to launch web-server`);
server.listen(0, '127.0.0.1', function (this: HttpOrHttpsServer, error?: any) {
if (error) {
reject(error);
} else {
resolve(this); // We return the this pointer which is the internal server object, which has access to the .address() method
}
});
});
}
async function closeServer(server: HttpOrHttpsServer): Promise<void> {
logger.log(`Closing web-server`);
await new Promise((resolve, reject) => {
server.close((error?: any) => {
if (error) {
logger.log('Error closing server in teardown: ' + (error && error.message));
reject(error);
} else {
resolve();
}
});
});
logger.log(`Web-server closed`);
}
/**
* Launch a web-server for the test project listening on the default port
*/
export class LaunchWebServer implements IFixture {
private constructor(private readonly _server: HttpOrHttpsServer, private readonly _testSpec: TestProjectSpec) { }
public static async launch(testSpec: TestProjectSpec): Promise<LaunchWebServer> {
return new LaunchWebServer(await createServerAsync(testSpec.props.webRoot), testSpec);
}
public get url(): URL {
const address = this._server.address();
return new URL(`http://localhost:${address.port}/`);
}
public get launchConfig(): ILaunchRequestArgs {
return Object.assign({}, this._testSpec.props.launchConfig, { url: this.url.toString() });
}
public get port(): number {
return this._server.address().port;
}
public async cleanUp(): Promise<void> {
await closeServer(this._server);
}
public toString(): string {
return `LaunchWebServer`;
}
}
export class ProvideStaticUrl implements IFixture {
public constructor(public readonly url: URL, private readonly testSpec: TestProjectSpec) {}
public get launchConfig(): ILaunchRequestArgs {
return {...this.testSpec.props.launchConfig, url: this.url.href };
}
cleanUp() {}
}

Просмотреть файл

@ -0,0 +1,19 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { IFixture } from './fixture';
import { asyncMap } from '../core-v2/chrome/collections/async';
/** Combine multiple fixtures into a single fixture, for easier management (e.g. you just need to call a single cleanUp method) */
export class MultipleFixtures implements IFixture {
private readonly _fixtures: IFixture[];
public constructor(...fixtures: IFixture[]) {
this._fixtures = fixtures;
}
public async cleanUp(): Promise<void> {
await asyncMap(this._fixtures, fixture => fixture.cleanUp());
}
}

Просмотреть файл

@ -0,0 +1,24 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { IFixture } from './fixture';
import { PromiseOrNot } from 'vscode-chrome-debug-core';
import { ITestCallbackContext } from 'mocha';
/** Run a test doing the setup/cleanup indicated by the provided fixtures */
function testUsingFunction<T extends IFixture>(
expectation: string,
fixtureProvider: (context: ITestCallbackContext) => PromiseOrNot<T>,
testFunction: (fixtures: T) => Promise<void>): Mocha.ITest {
return test(expectation, async function () {
const fixture = await fixtureProvider(this);
try {
await testFunction(fixture);
} finally {
await fixture.cleanUp();
}
});
}
export const testUsing = testUsingFunction;

Просмотреть файл

@ -11,20 +11,22 @@ import * as path from 'path';
import * as testSetup from '../testSetup';
import { setBreakpoint, setConditionalBreakpoint } from '../intTestSupport';
import { puppeteerSuite, puppeteerTest } from '../puppeteer/puppeteerSuite';
import { FrameworkTestSuite } from './frameworkCommonTests';
import { FrameworkTestSuite, testBreakOnLoad } from './frameworkCommonTests';
import { TestProjectSpec } from './frameworkTestSupport';
const DATA_ROOT = testSetup.DATA_ROOT;
const REACT_PROJECT_ROOT = path.join(DATA_ROOT, 'react', 'dist');
const TEST_SPEC = new TestProjectSpec( { projectRoot: REACT_PROJECT_ROOT } );
// This test doesn't use puppeteer, so we leave it outside the suite
testBreakOnLoad('React', TEST_SPEC, 'react_App_render');
puppeteerSuite('React Framework Tests', TEST_SPEC, (suiteContext) => {
suite('Common Framework Tests', () => {
const frameworkTests = new FrameworkTestSuite('React', suiteContext);
frameworkTests.testPageReloadBreakpoint('react_App_render');
frameworkTests.testPauseExecution();
frameworkTests.testBreakOnLoad('react_App_render');
frameworkTests.testStepOver('react_Counter_increment');
frameworkTests.testStepOut('react_Counter_increment', 'react_Counter_stepOut');
frameworkTests.testStepIn('react_Counter_stepInStop', 'react_Counter_stepIn');
@ -33,17 +35,23 @@ puppeteerSuite('React Framework Tests', TEST_SPEC, (suiteContext) => {
suite('React specific tests', () => {
puppeteerTest('Should hit breakpoint in .jsx file', suiteContext, async (_context, page) => {
const pausedWizard = suiteContext.launchProject!.pausedWizard;
const location = suiteContext.breakpointLabels.get('react_Counter_increment');
const incBtn = await page.waitForSelector('#incrementBtn');
await setBreakpoint(suiteContext.debugClient, location);
const clicked = incBtn.click();
await suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
await suiteContext.debugClient.continueRequest();
await pausedWizard.waitAndConsumePausedEvent(() => {});
await pausedWizard.resume();
await clicked;
});
puppeteerTest('Should hit conditional breakpoint in .jsx file', suiteContext, async (_context, page) => {
const pausedWizard = suiteContext.launchProject!.pausedWizard;
const location = suiteContext.breakpointLabels.get('react_Counter_increment');
const incBtn = await page.waitForSelector('#incrementBtn');
@ -54,9 +62,11 @@ puppeteerSuite('React Framework Tests', TEST_SPEC, (suiteContext) => {
// don't await the last click, as the stopped debugger will deadlock it
const clicked = incBtn.click();
await suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
await pausedWizard.waitAndConsumePausedEvent(() => {});
// Be sure to await the continue request, otherwise sometimes the last click promise will
// be rejected because the chrome instance is closed before it completes.
await suiteContext.debugClient.continueRequest();
await pausedWizard.resume();
await clicked;
});

Просмотреть файл

@ -1,19 +1,21 @@
import { BreakpointLocation } from '../intTestSupport';
import { loadProjectLabels } from '../labels';
import { expect } from 'chai';
import * as path from 'path';
suite('Test framework tests', () => {
test('Should correctly find breakpoint labels in test source files', async () => {
let labels = await loadProjectLabels('./testdata');
let worldLabel = labels.get('WorldLabel');
expect(worldLabel).to.eql(<BreakpointLocation>{ path: 'testdata\\labelTest.ts', line: 9 });
expect(worldLabel.path).to.eql(path.join('testdata', 'labelTest.ts'));
expect(worldLabel.line).to.eql(9);
});
test('Should correctly find block comment breakpoint labels in test source files', async () => {
let labels = await loadProjectLabels('./testdata');
let blockLabel = labels.get('blockLabel');
expect(blockLabel).to.eql(<BreakpointLocation>{ path: 'testdata\\labelTest.ts', line: 10 });
expect(blockLabel.path).to.eql(path.join('testdata', 'labelTest.ts'));
expect(blockLabel.line).to.eql(10);
});
});

Просмотреть файл

@ -3,9 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { puppeteerTest } from '../puppeteer/puppeteerSuite';
import { puppeteerTest, PuppeteerTestContext } from '../puppeteer/puppeteerSuite';
import { setBreakpoint } from '../intTestSupport';
import { FrameworkTestContext } from './frameworkTestSupport';
import { LaunchWebServer } from '../fixtures/launchWebServer';
import { loadProjectLabels } from '../labels';
import { TestProjectSpec } from './frameworkTestSupport';
import { DefaultFixture } from '../fixtures/defaultFixture';
import { MultipleFixtures } from '../fixtures/multipleFixtures';
import * as puppeteer from 'puppeteer';
import { PausedWizard } from '../wizards/pausedWizard';
import { BreakpointsWizard } from '../wizards/breakpoints/breakpointsWizard';
/**
* A common framework test suite that allows for easy (one-liner) testing of various
@ -15,20 +22,11 @@ import { FrameworkTestContext } from './frameworkTestSupport';
export class FrameworkTestSuite {
constructor(
private frameworkName: string,
private suiteContext: FrameworkTestContext
private suiteContext: PuppeteerTestContext
) {}
/**
* Test that we can stop on a breakpoint set before launch
* @param bpLabel Label for the breakpoint to set
*/
testBreakOnLoad(bpLabel: string) {
return test(`${this.frameworkName} - Should stop on breakpoint on initial page load`, async () => {
const testSpec = this.suiteContext.testSpec;
const location = this.suiteContext.breakpointLabels.get(bpLabel);
await this.suiteContext.debugClient
.hitBreakpointUnverified(testSpec.props.launchConfig, location);
});
private get pausedWizard(): PausedWizard {
return this.suiteContext.launchProject!.pausedWizard;
}
/**
@ -46,8 +44,13 @@ export class FrameworkTestSuite {
await setBreakpoint(debugClient, bpLocation);
const reloaded = page.reload();
await debugClient.assertStoppedLocation('breakpoint', bpLocation);
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
await debugClient.continueRequest();
await this.pausedWizard.waitAndConsumeResumedEvent();
await reloaded;
});
}
@ -68,10 +71,16 @@ export class FrameworkTestSuite {
await setBreakpoint(this.suiteContext.debugClient, location);
const clicked = incBtn.click();
await this.suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
const stopOnStep = this.suiteContext.debugClient.assertStoppedLocation('step', stepInLocation);
await this.suiteContext.debugClient.stepInAndStop();
await this.pausedWizard.waitAndConsumeResumedEvent();
await stopOnStep;
await this.suiteContext.debugClient.continueRequest();
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
await this.pausedWizard.resume();
await clicked;
});
}
@ -90,10 +99,16 @@ export class FrameworkTestSuite {
await setBreakpoint(this.suiteContext.debugClient, location);
const clicked = incBtn.click();
await this.suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
const stopOnStep = this.suiteContext.debugClient.assertStoppedLocation('step', { ...location, line: location.line + 1 });
await this.suiteContext.debugClient.nextAndStop();
await this.pausedWizard.waitAndConsumeResumedEvent();
await stopOnStep;
await this.suiteContext.debugClient.continueRequest();
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
await this.pausedWizard.resume();
await clicked;
});
}
@ -113,10 +128,16 @@ export class FrameworkTestSuite {
await setBreakpoint(this.suiteContext.debugClient, location);
const clicked = incBtn.click();
await this.suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
const stopOnStep = this.suiteContext.debugClient.assertStoppedLocation('step', stepOutLocation);
await this.suiteContext.debugClient.stepOutAndStop();
await this.pausedWizard.waitAndConsumeResumedEvent();
await stopOnStep;
await this.suiteContext.debugClient.continueRequest();
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
await this.pausedWizard.resume();
await clicked;
});
}
@ -126,11 +147,56 @@ export class FrameworkTestSuite {
* @param bpLocation
*/
testPauseExecution() {
return puppeteerTest(`${this.frameworkName} - Should correctly pause execution on a pause request`, this.suiteContext, async (context, _page) => {
const debugClient = context.debugClient;
await debugClient.pauseRequest({ threadId: 0 });
await debugClient.waitForEvent('stopped');
await debugClient.continueRequest();
return puppeteerTest(`${this.frameworkName} - Should correctly pause execution on a pause request`, this.suiteContext, async (_context, _page) => {
await this.pausedWizard.pause();
// TODO: Verify we are actually pausing in the expected line
await this.pausedWizard.resume();
});
}
/**
* A generic breakpoint test. This can be used for many different types of breakpoint tests with the following structure:
*
* 1. Wait for the page to load by waiting for the selector: `waitSelectorId`
* 2. Set a breakpoint at `bpLabel`
* 3. Execute a trigger event that should cause the breakpoint to be hit using the function `trigger`
* 4. Assert that the breakpoint is hit on the expected location, and continue
*
* @param waitSelector an html selector to identify a resource to wait for for page load
* @param bpLabel
* @param trigger
*/
testBreakpointHitsOnPageAction(description: string, waitSelector: string, file: string, bpLabel: string, trigger: (page: puppeteer.Page) => Promise<void>) {
return puppeteerTest(`${this.frameworkName} - ${description}`, this.suiteContext, async (context, page) => {
await page.waitForSelector(`${waitSelector}`);
const breakpoints = BreakpointsWizard.create(context.debugClient, context.testSpec);
const breakpointWizard = breakpoints.at(file);
const bp = await breakpointWizard.breakpoint({ text: bpLabel });
await bp.assertIsHitThenResumeWhen(() => trigger(page));
});
}
}
/**
* Test that we can stop on a breakpoint set before launch
* @param bpLabel Label for the breakpoint to set
*/
export function testBreakOnLoad(frameworkName: string, testSpec: TestProjectSpec, bpLabel: string) {
const testTitle = `${frameworkName} - Should stop on breakpoint on initial page load`;
return test(testTitle, async () => {
const defaultFixture = await DefaultFixture.createWithTitle(testTitle);
const launchWebServer = await LaunchWebServer.launch(testSpec);
const fixture = new MultipleFixtures(launchWebServer, defaultFixture);
try {
const breakpointLabels = await loadProjectLabels(testSpec.props.webRoot);
const location = breakpointLabels.get(bpLabel);
await defaultFixture.debugClient
.hitBreakpointUnverified(launchWebServer.launchConfig, location);
} finally {
await fixture.cleanUp();
}
});
}

Просмотреть файл

@ -4,9 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as puppeteer from 'puppeteer';
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
import { BreakpointLocation } from '../intTestSupport';
import { ILaunchRequestArgs } from '../../../src/chromeDebugInterfaces';
import { IValidatedMap } from '../core-v2/chrome/collections/validatedMap';
import { DATA_ROOT } from '../testSetup';
/*
* A collection of supporting classes/functions for running framework tests
@ -20,13 +23,9 @@ export interface ProjectSpecProps {
/** The directory from which to host the project for a test */
webRoot?: string;
/** The outfiles directory for the test project */
outFiles?: string;
outFiles?: string[];
/** The default launch configuration for the test project */
launchConfig?: ILaunchRequestArgs;
/** Port to use for the server */
port?: number;
/** Url to use for the project */
url?: string;
}
/**
@ -34,30 +33,44 @@ export interface ProjectSpecProps {
* attached to in order to test the debug adapter)
*/
export class TestProjectSpec {
_props: ProjectSpecProps;
_props: Required<ProjectSpecProps>;
get props() { return this._props; }
/**
* @param props Parameters for the project spec. The only required param is "projectRoot", others will be set to sensible defaults
*/
constructor(props: ProjectSpecProps) {
this._props = props;
this._props.projectSrc = props.projectSrc || path.join(props.projectRoot, 'src');
this._props.webRoot = props.webRoot || props.projectRoot;
this._props.outFiles = props.outFiles || path.join(props.projectRoot, 'out');
this._props.port = props.port || 7890;
this._props.url = props.url || `http://localhost:${props.port}/`;
this._props.launchConfig = props.launchConfig || {
url: props.url,
outFiles: [props.outFiles],
sourceMaps: true,
/* TODO: get this dynamically */
runtimeExecutable: 'node_modules/puppeteer/.local-chromium/win64-637110/chrome-win/chrome.exe',
webRoot: props.webRoot
constructor(props: ProjectSpecProps, public readonly staticUrl?: string) {
const outFiles = props.outFiles || [path.join(props.projectRoot, 'out')];
const webRoot = props.webRoot || props.projectRoot;
this._props = {
projectRoot: props.projectRoot,
projectSrc: props.projectSrc || path.join(props.projectRoot, 'src'),
webRoot: webRoot,
outFiles: outFiles,
launchConfig: props.launchConfig || {
outFiles: outFiles,
sourceMaps: true,
runtimeExecutable: puppeteer.executablePath(),
webRoot: webRoot
}
};
}
/**
* Specify project by it's location relative to the testdata folder e.g.:
* - TestProjectSpec.fromTestPath('react_with_loop/dist')
* - TestProjectSpec.fromTestPath('simple')
*
* The path *can only* use forward-slahes "/" as separators
*/
public static fromTestPath(reversedSlashesRelativePath: string, sourceDir = 'src', staticUrl?: string): TestProjectSpec {
const pathComponents = reversedSlashesRelativePath.split('/');
const projectAbsolutePath = path.join(...[DATA_ROOT].concat(pathComponents));
const projectSrc = path.join(projectAbsolutePath, sourceDir);
let props: ProjectSpecProps = { projectRoot: projectAbsolutePath, projectSrc };
return new TestProjectSpec(props, staticUrl);
}
/**
* Returns the full path to a source file
* @param filename
@ -72,9 +85,48 @@ export class TestProjectSpec {
*/
export interface FrameworkTestContext {
/** The test project specs for the currently executing test suite */
testSpec: TestProjectSpec;
readonly testSpec: TestProjectSpec;
/** A mapping of labels set in source files to a breakpoint location for a test */
breakpointLabels?: Map<string, BreakpointLocation>;
readonly breakpointLabels: IValidatedMap<string, BreakpointLocation>;
/** The debug adapter test support client */
debugClient?: ExtendedDebugClient;
readonly debugClient: ExtendedDebugClient;
}
export class ReassignableFrameworkTestContext implements FrameworkTestContext {
private _wrapped: FrameworkTestContext = new NotInitializedFrameworkTestContext();
public get testSpec(): TestProjectSpec {
return this._wrapped.testSpec;
}
public get breakpointLabels(): IValidatedMap<string, BreakpointLocation> {
return this._wrapped.breakpointLabels;
}
public get debugClient(): ExtendedDebugClient {
return this._wrapped.debugClient;
}
public reassignTo(newWrapped: FrameworkTestContext): this {
this._wrapped = newWrapped;
return this;
}
}
export class NotInitializedFrameworkTestContext implements FrameworkTestContext {
public get testSpec(): TestProjectSpec {
return this.throwNotInitializedException();
}
public get breakpointLabels(): IValidatedMap<string, BreakpointLocation> {
return this.throwNotInitializedException();
}
public get debugClient(): ExtendedDebugClient {
return this.throwNotInitializedException();
}
private throwNotInitializedException(): never {
throw new Error(`This test context hasn't been initialized yet. This is probably a bug in the tests`);
}
}

Просмотреть файл

@ -9,18 +9,26 @@
import { DebugClient } from 'vscode-debugadapter-testsupport';
const ImplementsBreakpointLocation = Symbol();
/**
* Simple breakpoint location params (based on what the debug test client accepts)
*/
export interface BreakpointLocation {
/** The path to the source file in which to set a breakpoint */
path: string;
/** The line number in the file to set a breakpoint on */
line: number;
/** Optional breakpoint column */
column?: number;
/** Whether or not we should assert if the bp is verified or not */
verified?: boolean;
export class BreakpointLocation {
[ImplementsBreakpointLocation]: 'BreakpointLocation';
public constructor (
/** The path to the source file in which to set a breakpoint */
public readonly path: string,
/** The line number in the file to set a breakpoint on */
public readonly line: number,
/** Optional breakpoint column */
public readonly column?: number,
/** Whether or not we should assert if the bp is verified or not */
public readonly verified?: boolean) {}
public toString(): string {
return `${this.path}:${this.line}:${this.column} verified: ${this.verified}`;
}
}
/**

Просмотреть файл

@ -8,6 +8,7 @@ import * as fs from 'fs';
import * as util from 'util';
import * as readline from 'readline';
import * as path from 'path';
import { ValidatedMap, IValidatedMap } from './core-v2/chrome/collections/validatedMap';
/*
* Contains classes and functions to find and use test breakpoint labels in test project files
@ -16,7 +17,7 @@ import * as path from 'path';
const readdirAsync = util.promisify(fs.readdir);
const labelRegex = /(\/\/|\/\*)\s*bpLabel:\s*(.+?)\b/;
const ignoreList = [ 'node_modules', '.git' ];
const ignoreList = [ 'node_modules', '.git', path.join('dist', 'out'), path.join('testdata', 'react', 'src') ];
/**
* A label in a source file that tells us where to put a breakpoint for a specific test
@ -30,15 +31,15 @@ export interface BreakpointLabel {
* Load all breakpoint labels that exist in the 'projectRoot' directory
* @param projectRoot Root directory for the test project
*/
export async function loadProjectLabels(projectRoot: string): Promise<Map<string, BreakpointLocation>> {
export async function loadProjectLabels(projectRoot: string): Promise<IValidatedMap<string, BreakpointLocation>> {
const labelMap = new Map<string, BreakpointLocation>();
const labelMap = new ValidatedMap<string, BreakpointLocation>();
if (containsIgnores(projectRoot)) return labelMap;
const files = await readdirAsync(projectRoot);
for (let file of files) {
let subMap: Map<string, BreakpointLocation> = null;
let subMap: Map<string, BreakpointLocation> | null = null;
const fullPath = path.join(projectRoot, file);
if (fs.lstatSync(fullPath).isDirectory()) {
subMap = await loadProjectLabels(fullPath);
@ -71,7 +72,7 @@ export async function loadLabelsFromFile(filePath: string): Promise<Map<string,
let match = labelRegex.exec(fileLine);
if (match) {
labelMap.set(match[2], { path: filePath, line: lineNumber });
labelMap.set(match[2], new BreakpointLocation(filePath, lineNumber));
}
lineNumber++;
});

Просмотреть файл

@ -0,0 +1,59 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as getPort from 'get-port';
import { IFixture } from '../fixtures/fixture';
import { launchTestAdapter } from '../intTestSupport';
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
import { connectPuppeteer, getPageByUrl } from './puppeteerSupport';
import { logCallsTo } from '../utils/logging';
import { isThisV1 } from '../testSetup';
import { Browser, Page } from 'puppeteer';
import { ILaunchRequestArgs } from 'vscode-chrome-debug-core';
import { logger } from 'vscode-debugadapter';
/**
* Launch the debug adapter using the Puppeteer version of chrome, and then connect to it
*
* The fixture offers access to both the browser, and page objects of puppeteer
*/
export class LaunchPuppeteer implements IFixture {
public constructor(public readonly browser: Browser, public readonly page: Page) { }
public static async create(debugClient: ExtendedDebugClient, launchConfig: ILaunchRequestArgs): Promise<LaunchPuppeteer> {
const daPort = await getPort();
logger.log(`About to launch debug-adapter at port: ${daPort}`);
await launchTestAdapter(debugClient, Object.assign({}, launchConfig, { port: daPort }));
const browser = await connectPuppeteer(daPort);
const page = logCallsTo(await getPageByUrl(browser, launchConfig.url!), 'PuppeteerPage');
// This short wait appears to be necessary to completely avoid a race condition in V1 (tried several other
// strategies to wait deterministically for all scripts to be loaded and parsed, but have been unsuccessful so far)
// If we don't wait here, there's always a possibility that we can send the set breakpoint request
// for a subsequent test after the scripts have started being parsed/run by Chrome, yet before
// the target script is parsed, in which case the adapter will try to use breakOnLoad, but
// the instrumentation BP will never be hit, leaving our breakpoint in limbo
if (isThisV1) {
await new Promise(a => setTimeout(a, 500));
}
return new LaunchPuppeteer(browser, page);
}
public async cleanUp(): Promise<void> {
logger.log(`Closing puppeteer and chrome`);
try {
await this.browser.close();
logger.log(`Scucesfully closed puppeteer and chrome`);
} catch (exception) {
logger.log(`Failed to close puppeteer: ${exception}`);
}
}
public toString(): string {
return `LaunchPuppeteer`;
}
}

Просмотреть файл

@ -3,23 +3,55 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createServer } from 'http-server';
import * as puppeteer from 'puppeteer';
import * as testSetup from '../testSetup';
import { launchTestAdapter } from '../intTestSupport';
import { getPageByUrl, connectPuppeteer } from './puppeteerSupport';
import { FrameworkTestContext, TestProjectSpec } from '../framework/frameworkTestSupport';
import { FrameworkTestContext, TestProjectSpec, ReassignableFrameworkTestContext } from '../framework/frameworkTestSupport';
import { loadProjectLabels } from '../labels';
import { isThisV2, isThisV1 } from '../testSetup';
import { ISuiteCallbackContext, ISuite } from 'mocha';
import { NullFixture } from '../fixtures/fixture';
import { LaunchProject } from '../fixtures/launchProject';
import { PromiseOrNot } from 'vscode-chrome-debug-core';
import { logger } from 'vscode-debugadapter';
import { setTestLogName } from '../utils/logging';
/**
* Extends the normal debug adapter context to include context relevant to puppeteer tests.
*/
export interface PuppeteerTestContext extends FrameworkTestContext {
export interface IPuppeteerTestContext extends FrameworkTestContext {
/** The connected puppeteer browser object */
browser: puppeteer.Browser;
browser: puppeteer.Browser | null;
/** The currently running html page in Chrome */
page: puppeteer.Page;
page: puppeteer.Page | null;
launchProject: LaunchProject | null;
}
export class PuppeteerTestContext extends ReassignableFrameworkTestContext {
private _browser: puppeteer.Browser | null = null;
private _page: puppeteer.Page | null = null;
private _launchProject: LaunchProject | null = null;
public constructor() {
super();
}
public get browser(): puppeteer.Browser | null {
return this._browser;
}
public get page(): puppeteer.Page | null {
return this._page;
}
public get launchProject(): LaunchProject | null {
return this._launchProject;
}
public reassignTo(newWrapped: IPuppeteerTestContext): this {
super.reassignTo(newWrapped);
this._page = newWrapped.page;
this._browser = newWrapped.browser;
this._launchProject = newWrapped.launchProject;
return this;
}
}
/**
@ -30,31 +62,31 @@ export interface PuppeteerTestContext extends FrameworkTestContext {
* @param context The test context for this test sutie
* @param testFunction The inner test function that will run a test using puppeteer
*/
export async function puppeteerTest(
function puppeteerTestFunction(
description: string,
context: FrameworkTestContext,
testFunction: (context: PuppeteerTestContext, page: puppeteer.Page) => Promise<any>
) {
return test(description, async function () {
const debugClient = await context.debugClient;
await launchTestAdapter(debugClient, context.testSpec.props.launchConfig);
const browser = await connectPuppeteer(9222);
const page = await getPageByUrl(browser, context.testSpec.props.url);
// This short wait appears to be necessary to completely avoid a race condition in V1 (tried several other
// strategies to wait deterministically for all scripts to be loaded and parsed, but have been unsuccessful so far)
// If we don't wait here, there's always a possibility that we can send the set breakpoint request
// for a subsequent test after the scripts have started being parsed/run by Chrome, yet before
// the target script is parsed, in which case the adapter will try to use breakOnLoad, but
// the instrumentation BP will never be hit, leaving our breakpoint in limbo
if (isThisV1) {
await new Promise(a => setTimeout(a, 500));
}
await testFunction({ ...context, browser, page }, page);
context: PuppeteerTestContext,
testFunction: (context: PuppeteerTestContext, page: puppeteer.Page) => PromiseOrNot<void>,
functionToDeclareTest: (description: string, callback: (this: ISuiteCallbackContext) => void) => ISuite = test
): void {
functionToDeclareTest(description, function () {
return testFunction(context, context.page!);
});
}
puppeteerTestFunction.skip = (
description: string,
_context: PuppeteerTestContext,
_testFunction: (context: IPuppeteerTestContext, page: puppeteer.Page) => Promise<any>
) => test.skip(description, () => { throw new Error(`We don't expect this to be called`); });
puppeteerTestFunction.only = (
description: string,
context: PuppeteerTestContext,
testFunction: (context: IPuppeteerTestContext, page: puppeteer.Page) => Promise<any>
) => puppeteerTestFunction(description, context, testFunction, test.only);
export const puppeteerTest = puppeteerTestFunction;
/**
* Defines a custom test suite which will:
* 1) automatically launch a server from a test project directory,
@ -67,40 +99,50 @@ export async function puppeteerTest(
* @param testSpec Info about the test project on which this suite will be based
* @param callback The inner test suite that uses this context
*/
export function puppeteerSuite(
function puppeteerSuiteFunction(
description: string,
testSpec: TestProjectSpec,
callback: (suiteContext: FrameworkTestContext) => any
): Mocha.ISuite {
return suite(description, () => {
let suiteContext: FrameworkTestContext = { testSpec };
callback: (suiteContext: PuppeteerTestContext) => void,
suiteFunctionToUse: (description: string, callback: (this: ISuiteCallbackContext) => void) => ISuite | void = suite
): Mocha.ISuite | void {
return suiteFunctionToUse(description, () => {
let testContext = new PuppeteerTestContext();
let fixture: LaunchProject | NullFixture = new NullFixture(); // This variable is shared across all test of this suite
let server: any;
setup(async function () {
setTestLogName(this.currentTest.fullTitle());
const breakpointLabels = await loadProjectLabels(testSpec.props.webRoot);
const launchProject = fixture = await LaunchProject.create(this, testSpec);
setup(async () => {
const portNumber = process.env['MSFT_TEST_DA_PORT']
? parseInt(process.env['MSFT_TEST_DA_PORT'], 10)
: undefined;
let debugClient = await testSetup.setup(portNumber);
suiteContext.debugClient = debugClient;
await suiteContext.debugClient;
// Running tests on CI can time out at the default 5s, so we up this to 10s
suiteContext.breakpointLabels = await loadProjectLabels(testSpec.props.webRoot);
suiteContext.debugClient.defaultTimeout = 15000;
server = createServer({ root: testSpec.props.webRoot });
server.listen(7890);
testContext.reassignTo({
testSpec, debugClient: launchProject.debugClient, breakpointLabels, browser: launchProject.browser, page: launchProject.page, launchProject
});
});
teardown(() => {
if (server) {
server.close();
}
return testSetup.teardown();
teardown(async () => {
await fixture.cleanUp();
fixture = new NullFixture();
logger.log(`teardown finished`);
});
callback(suiteContext);
callback(testContext);
});
}
puppeteerSuiteFunction.skip = (
description: string,
testSpec: TestProjectSpec,
callback: (suiteContext: PuppeteerTestContext) => any
) => puppeteerSuiteFunction(description, testSpec, callback, suite.skip);
puppeteerSuiteFunction.only = (
description: string,
testSpec: TestProjectSpec,
callback: (suiteContext: PuppeteerTestContext) => any,
) => puppeteerSuiteFunction(description, testSpec, callback, suite.only);
puppeteerSuiteFunction.skip = (
description: string,
_testSpec: TestProjectSpec,
_callback: (suiteContext: PuppeteerTestContext) => any,
) => suite.skip(description, () => {});
export const puppeteerSuite = puppeteerSuiteFunction;

Просмотреть файл

@ -7,8 +7,7 @@
* Functions that make puppeteer testing easier
*/
import * as util from 'util';
import * as request from 'request';
import * as request from 'request-promise-native';
import * as puppeteer from 'puppeteer';
/**
@ -17,8 +16,8 @@ import * as puppeteer from 'puppeteer';
*/
export async function connectPuppeteer(port: number): Promise<puppeteer.Browser> {
const resp = await util.promisify(request)(`http://localhost:${port}/json/version`);
const { webSocketDebuggerUrl } = JSON.parse(resp.body);
const resp = await request(`http://localhost:${port}/json/version`);
const { webSocketDebuggerUrl } = JSON.parse(resp);
const browser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl, defaultViewport: null });
return browser;
@ -47,7 +46,7 @@ export async function getPageByUrl(browser: puppeteer.Browser, url: string, time
while ((current - before) < timeout) {
const pages = await browser.pages();
const desiredPage = pages.find(p => p.url() === url);
const desiredPage = pages.find(p => p.url().toLowerCase() === url.toLowerCase());
if (desiredPage) {
return desiredPage;
}

4
test/int/puppeteer/puppeteerTypeFix.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
// Needed to make @types/puppeteer pass type checking
// See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/24419
interface Element { }

Просмотреть файл

@ -5,3 +5,4 @@ import { TestProjectSpec } from '../framework/frameworkTestSupport';
const DATA_ROOT = testSetup.DATA_ROOT;
const REACT_PROJECT_ROOT = path.join(DATA_ROOT, 'react', 'dist');
export const reactTestSpecification = new TestProjectSpec({ projectRoot: REACT_PROJECT_ROOT });
export const reactWithLoopTestSpecification = new TestProjectSpec({ projectRoot: path.join(DATA_ROOT, 'react_with_loop', 'dist') });

Просмотреть файл

@ -2,118 +2,39 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as assert from 'assert';
import * as path from 'path';
import * as puppeteer from 'puppeteer';
import * as testSetup from './testSetup';
import { DebugProtocol } from 'vscode-debugprotocol';
import { FrameworkTestContext, TestProjectSpec } from './framework/frameworkTestSupport';
import { puppeteerSuite, puppeteerTest } from './puppeteer/puppeteerSuite';
import { setBreakpoint } from './intTestSupport';
import { THREAD_ID } from 'vscode-chrome-debug-core-testsupport';
import { URL } from 'url';
import { activate } from '../../src/extension';
import { BreakpointsWizard } from './wizards/breakpoints/breakpointsWizard';
import { ExpectedFrame } from './wizards/breakpoints/implementation/stackTraceObjectAssertions';
const DATA_ROOT = testSetup.DATA_ROOT;
const SIMPLE_PROJECT_ROOT = path.join(DATA_ROOT, 'stackTrace');
const TEST_SPEC = new TestProjectSpec( { projectRoot: SIMPLE_PROJECT_ROOT } );
const TEST_URL = new URL(TEST_SPEC.props.url);
interface ExpectedSource {
fileRelativePath?: string;
urlRelativePath?: string;
}
interface ExpectedFrame {
name: string;
line?: number;
column?: number;
source?: ExpectedSource;
presentationHint?: string;
}
function assertSourceMatches(actual: DebugProtocol.Source, expected: ExpectedSource, index: number) {
if (expected == null) {
assert.equal(actual, null, `Source was returned for frame ${index} but none was expected`);
return;
}
let expectedName: string;
let expectedPath: string;
if (expected.fileRelativePath) {
// Generate the expected path from the relative path and the project root
expectedPath = path.join(TEST_SPEC.props.projectRoot, expected.fileRelativePath);
expectedName = path.parse(expectedPath).base;
} else if (expected.urlRelativePath) {
// Generate the expected path from the relative path and the project url
const url = new URL(TEST_URL.toString()); // Clone URL so we can update it
url.pathname = expected.urlRelativePath;
expectedName = url.host;
expectedPath = url.toString();
}
assert.equal(actual.name, expectedName, `Source name for frame ${index} does not match`);
assert.equal(actual.path, expectedPath, `Source path for frame ${index} does not match`);
}
function assertFrameMatches(actual: DebugProtocol.StackFrame, expected: ExpectedFrame, index: number) {
assert.equal(actual.name, expected.name, `Name for frame ${index} does not match`);
assert.equal(actual.line, expected.line, `Line number for frame ${index} does not match`);
assert.equal(actual.column, expected.column, `Column number for frame ${index} does not match`);
assert.equal(actual.presentationHint, expected.presentationHint, `Presentation hint for frame ${index} does not match`);
assertSourceMatches(actual.source, expected.source, index);
}
function assertResponseMatches(actual: DebugProtocol.StackTraceResponse, expectedFrames: ExpectedFrame[]) {
// Check totalFrames property
assert.equal(actual.body.totalFrames, expectedFrames.length, 'Property "totalFrames" does not match number of expected frames');
// Check array length
const actualFrames = actual.body.stackFrames;
assert.equal(actualFrames.length, expectedFrames.length, 'Number of stack frames in array does not match');
// Check each frame
actualFrames.forEach((actualFrame, i) => {
assertFrameMatches(actualFrame, expectedFrames[i], i);
});
}
const TEST_SPEC = new TestProjectSpec( { projectRoot: SIMPLE_PROJECT_ROOT, projectSrc: SIMPLE_PROJECT_ROOT } );
interface StackTraceValidationConfig {
suiteContext: FrameworkTestContext;
page: puppeteer.Page;
breakPointLabel: string;
buttonIdToClick: string;
args: DebugProtocol.StackTraceArguments;
expectedFranes: ExpectedFrame[];
format?: DebugProtocol.StackFrameFormat;
expectedFrames: ExpectedFrame[];
}
async function validateStackTrace(config: StackTraceValidationConfig): Promise<void> {
// Find the breakpoint location for this test
const location = config.suiteContext.breakpointLabels.get(config.breakPointLabel);
// Set the breakpoint, click the button, and wait for the breakpoint to hit
const incBtn = await config.page.waitForSelector(config.buttonIdToClick);
await setBreakpoint(config.suiteContext.debugClient, location);
const clicked = incBtn.click();
await config.suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
// Get the stack trace
const stackTraceResponse = await config.suiteContext.debugClient.send('stackTrace', config.args);
const breakpoints = BreakpointsWizard.create(config.suiteContext.debugClient, TEST_SPEC);
const breakpointWizard = breakpoints.at('app.js');
// Clean up the test before assertions, in case the assertions fail
await config.suiteContext.debugClient.continueRequest();
await clicked;
const setStateBreakpoint = await breakpointWizard.breakpoint({
text: "console.log('Test stack trace here')"
});
// Assert the response is as expected
try {
assertResponseMatches(stackTraceResponse, config.expectedFranes);
} catch (e) {
const error: assert.AssertionError = e;
error.message += '\nActual stack trace response: \n' + JSON.stringify(stackTraceResponse, null, 2);
throw error;
}
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), {stackTrace: config.expectedFrames, stackFrameFormat: config.format});
}
puppeteerSuite('Stack Traces', TEST_SPEC, (suiteContext) => {
@ -123,37 +44,37 @@ puppeteerSuite('Stack Traces', TEST_SPEC, (suiteContext) => {
page: page,
breakPointLabel: 'stackTraceBreakpoint',
buttonIdToClick: '#button',
args: {
threadId: THREAD_ID
},
expectedFranes: [
{ name: '(anonymous function)', line: 7, column: 9, source: { fileRelativePath: 'app.js' }},
{ name: 'inner', line: 8, column: 7, source: { fileRelativePath: 'app.js' }},
format: {},
expectedFrames: [
{ name: '(anonymous function)', line: 11, column: 9, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: 'evalCallback', line: 12, column: 7, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: '(eval code)', line: 1, column: 1, source: { evalCode: true }, presentationHint: 'normal'},
{ name: 'timeoutCallback', line: 6, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: '[ setTimeout ]', presentationHint: 'label'},
{ name: 'buttonClick', line: 2, column: 5, source: { fileRelativePath: 'app.js' }},
{ name: 'onclick', line: 7, column: 49, source: { urlRelativePath: '/' }},
{ name: 'buttonClick', line: 2, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: 'onclick', line: 7, column: 49, source: { url: suiteContext.launchProject!.url }, presentationHint: 'normal'},
]
});
});
puppeteerTest('Stack trace is generated with module formatting', suiteContext, async (_context, page) => {
const url = suiteContext.launchProject!.url;
await validateStackTrace({
suiteContext: suiteContext,
page: page,
breakPointLabel: 'stackTraceBreakpoint',
buttonIdToClick: '#button',
args: {
threadId: THREAD_ID,
format: {
module: true
}
format: {
module: true
},
expectedFranes: [
{ name: '(anonymous function) [app.js]', line: 7, column: 9, source: { fileRelativePath: 'app.js' }},
{ name: 'inner [app.js]', line: 8, column: 7, source: { fileRelativePath: 'app.js' }},
expectedFrames: [
{ name: '(anonymous function) [app.js]', line: 11, column: 9, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: 'evalCallback [app.js]', line: 12, column: 7, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: /\(eval code\) \[.*VM.*]/, line: 1, column: 1, source: { evalCode: true }, presentationHint: 'normal'},
{ name: 'timeoutCallback [app.js]', line: 6, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: '[ setTimeout ]', presentationHint: 'label'},
{ name: 'buttonClick [app.js]', line: 2, column: 5, source: { fileRelativePath: 'app.js' }},
{ name: `onclick [${TEST_URL.host}]`, line: 7, column: 49, source: { urlRelativePath: '/' }},
{ name: 'buttonClick [app.js]', line: 2, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: `onclick [${url.host}]`, line: 7, column: 49, source: { url }, presentationHint: 'normal'},
]
});
});
@ -164,44 +85,43 @@ puppeteerSuite('Stack Traces', TEST_SPEC, (suiteContext) => {
page: page,
breakPointLabel: 'stackTraceBreakpoint',
buttonIdToClick: '#button',
args: {
threadId: THREAD_ID,
format: {
line: true,
}
format: {
line: true,
},
expectedFranes: [
{ name: '(anonymous function) Line 7', line: 7, column: 9, source: { fileRelativePath: 'app.js' }},
{ name: 'inner Line 8', line: 8, column: 7, source: { fileRelativePath: 'app.js' }},
expectedFrames: [
{ name: '(anonymous function) Line 11', line: 11, column: 9, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: 'evalCallback Line 12', line: 12, column: 7, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: '(eval code) Line 1', line: 1, column: 1, source: { evalCode: true }, presentationHint: 'normal'},
{ name: 'timeoutCallback Line 6', line: 6, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: '[ setTimeout ]', presentationHint: 'label'},
{ name: 'buttonClick Line 2', line: 2, column: 5, source: { fileRelativePath: 'app.js' }},
{ name: 'onclick Line 7', line: 7, column: 49, source: { urlRelativePath: '/' }},
{ name: 'buttonClick Line 2', line: 2, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: 'onclick Line 7', line: 7, column: 49, source: { url: suiteContext.launchProject!.url }, presentationHint: 'normal'},
]
});
});
puppeteerTest('Stack trace is generated with all formatting', suiteContext, async (_context, page) => {
const url = suiteContext.launchProject!.url;
await validateStackTrace({
suiteContext: suiteContext,
page: page,
breakPointLabel: 'stackTraceBreakpoint',
buttonIdToClick: '#button',
args: {
threadId: THREAD_ID,
format: {
parameters: true,
parameterTypes: true,
parameterNames: true,
line: true,
module: true
}
format: {
parameters: true,
parameterTypes: true,
parameterNames: true,
line: true,
module: true
},
expectedFranes: [
{ name: '(anonymous function) [app.js] Line 7', line: 7, column: 9, source: { fileRelativePath: 'app.js' }},
{ name: 'inner [app.js] Line 8', line: 8, column: 7, source: { fileRelativePath: 'app.js' }},
expectedFrames: [
{ name: '(anonymous function) [app.js] Line 11', line: 11, column: 9, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: 'evalCallback [app.js] Line 12', line: 12, column: 7, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: /\(eval code\) \[.*VM.*] Line 1/, line: 1, column: 1, source: { evalCode: true }, presentationHint: 'normal'},
{ name: 'timeoutCallback [app.js] Line 6', line: 6, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: '[ setTimeout ]', presentationHint: 'label'},
{ name: 'buttonClick [app.js] Line 2', line: 2, column: 5, source: { fileRelativePath: 'app.js' }},
{ name: `onclick [${TEST_URL.host}] Line 7`, line: 7, column: 49, source: { urlRelativePath: '/' }},
{ name: 'buttonClick [app.js] Line 2', line: 2, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
{ name: `onclick [${url.host}] Line 7`, line: 7, column: 49, source: { url }, presentationHint: 'normal'},
]
});
});

Просмотреть файл

@ -9,26 +9,28 @@ import { createServer } from 'http-server';
import * as ts from 'vscode-chrome-debug-core-testsupport';
import * as testSetup from './testSetup';
import { HttpOrHttpsServer } from './types/server';
suite('Stepping', () => {
const DATA_ROOT = testSetup.DATA_ROOT;
let dc: ts.ExtendedDebugClient;
setup(() => {
return testSetup.setup()
setup(function () {
return testSetup.setup(this)
.then(_dc => dc = _dc);
});
let server: any;
teardown(() => {
let server: HttpOrHttpsServer | null;
teardown(async () => {
if (server) {
server.close();
server.close(err => console.log('Error closing server in teardown: ' + (err && err.message)));
server = null;
}
return testSetup.teardown();
await testSetup.teardown();
});
suite('skipFiles', () => {
suite.skip('skipFiles', () => {
test('when generated script is skipped via regex, the source can be un-skipped', async () => {
const testProjectRoot = path.join(DATA_ROOT, 'calls-between-merged-files');
const sourceA = path.join(testProjectRoot, 'sourceA.ts');

Просмотреть файл

@ -5,21 +5,39 @@
import * as path from 'path';
import * as tmp from 'tmp';
import * as puppeteer from 'puppeteer';
import * as _ from 'lodash';
import * as ts from 'vscode-chrome-debug-core-testsupport';
import { ILaunchRequestArgs } from '../../src/chromeDebugInterfaces';
import { Dictionary } from 'lodash';
import { logCallsTo, getDebugAdapterLogFilePath, setTestLogName } from './utils/logging';
import { IBeforeAndAfterContext, ITestCallbackContext } from 'mocha';
import { killAllChrome } from '../testUtils';
import { DefaultTimeoutMultiplier } from './utils/waitUntilReadyWithTimeout';
const DEBUG_ADAPTER = './out/src/chromeDebug.js';
let testLaunchProps: ILaunchRequestArgs;
let testLaunchProps: ILaunchRequestArgs & Dictionary<unknown> | undefined;
export const isThisV2 = false;
export const isThisV2 = true;
export const isThisV1 = !isThisV2;
export const isWindows = process.platform === 'win32';
function formLaunchArgs(launchArgs: ILaunchRequestArgs): void {
function formLaunchArgs(launchArgs: ILaunchRequestArgs & Dictionary<unknown>, testTitle: string): void {
launchArgs.trace = 'verbose';
launchArgs.logTimestamps = true;
launchArgs.disableNetworkCache = true;
launchArgs.logFilePath = getDebugAdapterLogFilePath(testTitle);
if (!launchArgs.runtimeExecutable) {
launchArgs.runtimeExecutable = puppeteer.executablePath();
}
const hideWindows = process.env['TEST_DA_HIDE_WINDOWS'] === 'true';
if (hideWindows) {
launchArgs.runtimeArgs = ['--headless', '--disable-gpu'];
}
// Start with a clean userDataDir for each test run
const tmpDir = tmp.dirSync({ prefix: 'chrome2-' });
@ -32,21 +50,55 @@ function formLaunchArgs(launchArgs: ILaunchRequestArgs): void {
}
}
function patchLaunchArgs(launchArgs: ILaunchRequestArgs): void {
formLaunchArgs(launchArgs);
function patchLaunchArgs(launchArgs: ILaunchRequestArgs, testTitle: string): void {
formLaunchArgs(launchArgs, testTitle);
}
export const lowercaseDriveLetterDirname = __dirname.charAt(0).toLowerCase() + __dirname.substr(1);
export const PROJECT_ROOT = path.join(lowercaseDriveLetterDirname, '../../../');
export const DATA_ROOT = path.join(PROJECT_ROOT, 'testdata/');
export function setup(port?: number, launchProps?: ILaunchRequestArgs) {
/** Default setup for all our tests, using the context of the setup method
* - Best practise: The new best practise is to use the DefaultFixture when possible instead of calling this method directly
*/
export async function setup(context: IBeforeAndAfterContext | ITestCallbackContext, port?: number, launchProps?: ILaunchRequestArgs): Promise<ts.ExtendedDebugClient> {
const currentTest = _.defaultTo(context.currentTest, context.test);
return setupWithTitle(currentTest.fullTitle(), port, launchProps);
}
/** Default setup for all our tests, using the test title
* - Best practise: The new best practise is to use the DefaultFixture when possible instead of calling this method directly
*/
export async function setupWithTitle(testTitle: string, port?: number, launchProps?: ILaunchRequestArgs): Promise<ts.ExtendedDebugClient> {
// killAllChromesOnWin32(); // Kill chrome.exe instances before the tests. Killing them after the tests is not as reliable. If setup fails, teardown is not executed.
setTestLogName(testTitle);
if (!port) {
const daPort = process.env['TEST_DA_PORT'];
port = daPort
? parseInt(daPort, 10)
: undefined;
}
if (launchProps) {
testLaunchProps = launchProps;
}
return ts.setup({entryPoint: DEBUG_ADAPTER, type: 'chrome', patchLaunchArgs: patchLaunchArgs, port: port});
const debugClient = await ts.setup({ entryPoint: DEBUG_ADAPTER, type: 'chrome', patchLaunchArgs: args => patchLaunchArgs(args, testTitle), port: port });
debugClient.defaultTimeout = DefaultTimeoutMultiplier * 10000 /*10 seconds*/;
const wrappedDebugClient = logCallsTo(debugClient, 'DebugAdapterClient');
return wrappedDebugClient;
}
export function teardown() {
return ts.teardown();
export async function teardown() {
await ts.teardown();
}
export function killAllChromesOnWin32() {
if (process.platform === 'win32') {
// We only need to kill the chrome.exe instances on the Windows agent
// TODO: Figure out a way to remove this
killAllChrome();
}
}

12
test/int/types/object.ts Normal file
Просмотреть файл

@ -0,0 +1,12 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
// Improve the default typing of Object.keys(o: T) to be keyof T (without the symbols)
interface ObjectConstructor {
keys<T>(o: T): WithoutSymbols<(keyof T)>[];
}
declare var Object: ObjectConstructor;
/** Return the strings that form S and ignore the symbols */
type WithoutSymbols<S> = S extends string ? S : string;

4
test/int/types/server.ts Normal file
Просмотреть файл

@ -0,0 +1,4 @@
import * as http from 'http';
import * as https from 'https';
export type HttpOrHttpsServer = http.Server | https.Server;

Просмотреть файл

@ -1,5 +1,5 @@
import * as _ from 'lodash';
import { createColumnNumber, createLineNumber } from '../core-v2/chrome/internal/locations/subtypes';
import { createColumnNumber, createLineNumber, LineNumber } from '../core-v2/chrome/internal/locations/subtypes';
import { utils } from 'vscode-chrome-debug-core';
import { Position } from '../core-v2/chrome/internal/locations/location';
@ -9,8 +9,7 @@ export async function findPositionOfTextInFile(filePath: string, text: string):
const textStartIndex = contents.indexOf(text);
if (textStartIndex >= 0) {
const contentsBeforeText = contents.substr(0, textStartIndex);
const textLineNumber = createLineNumber(_.countBy(contentsBeforeText, c => c === '\n')['true'] || 0);
const textLineNumber = findLineNumber(contents, textStartIndex);
const lastNewLineBeforeTextIndex = contents.lastIndexOf('\n', textStartIndex);
const textColumNumber = createColumnNumber(textStartIndex - (lastNewLineBeforeTextIndex + 1));
return new Position(textLineNumber, textColumNumber);
@ -18,3 +17,10 @@ export async function findPositionOfTextInFile(filePath: string, text: string):
throw new Error(`Couldn't find ${text} in ${filePath}`);
}
}
export function findLineNumber(contents: string, characterIndex: number): LineNumber {
const contentsBeforeCharacter = contents.substr(0, characterIndex);
const textLineNumber = createLineNumber(_.countBy(contentsBeforeCharacter, c => c === '\n')['true'] || 0);
return textLineNumber;
}

Просмотреть файл

@ -0,0 +1,68 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { utils } from 'vscode-chrome-debug-core';
/**
* The test normally run very fast, so it's difficult to see what actions they are taking in the browser.
* We can use the HumanSlownessSimulator to artifically slow some classes like the puppeteer classes, so it's the actions
* will be taken at a lower speed, and it'll be easier to see and understand what is happening
*/
export class HumanSlownessSimulator {
public constructor(private readonly _slownessInMillisecondsValueGenerator: () => number = () => 500) { }
public simulateSlowness(): Promise<void> {
return utils.promiseTimeout(undefined, this._slownessInMillisecondsValueGenerator());
}
public wrap<T extends object>(object: T): T {
return new HumanSpeedProxy(this, object).wrapped();
}
}
export class HumanSpeedProxy<T extends object> {
constructor(
private readonly _humanSlownessSimulator: HumanSlownessSimulator,
private readonly _objectToWrap: T) {
}
public wrapped(): T {
const handler = {
get: <K extends keyof T>(target: T, propertyKey: K, _receiver: any) => {
this._humanSlownessSimulator.simulateSlowness();
const originalPropertyValue = target[propertyKey];
if (typeof originalPropertyValue === 'function') {
return (...args: any) => {
const result = originalPropertyValue.apply(target, args);
if (result && result.then) {
// Currently we only slow down async operations
return result.then(async (promiseResult: object) => {
await this._humanSlownessSimulator.simulateSlowness();
return typeof promiseResult === 'object'
? this._humanSlownessSimulator.wrap(promiseResult)
: promiseResult;
}, (rejection: unknown) => {
return rejection;
});
}
};
} else {
return originalPropertyValue;
}
}
};
return new Proxy<T>(this._objectToWrap, handler);
}
}
const humanSlownessSimulator = new HumanSlownessSimulator();
const humanSlownessEnabeld = process.env.RUN_TESTS_SLOWLY === 'true';
export function slowToHumanLevel<T extends object>(object: T): T {
return humanSlownessEnabeld
? humanSlownessSimulator.wrap(object)
: object;
}

74
test/int/utils/logging.ts Normal file
Просмотреть файл

@ -0,0 +1,74 @@
import * as path from 'path';
import { logger, } from 'vscode-debugadapter';
import { LogLevel } from 'vscode-debugadapter/lib/logger';
import { MethodsCalledLogger, IMethodsCalledLoggerConfiguration, MethodsCalledLoggerConfiguration, ReplacementInstruction, wrapWithMethodLogger } from '../core-v2/chrome/logging/methodsCalledLogger';
const useDateTimeInLog = false;
function dateTimeForFilePath(): string {
return new Date().toISOString().replace(/:/g, '').replace('T', ' ').replace(/\.[0-9]+^/, '');
}
function dateTimeForFilePathIfNeeded() {
return useDateTimeInLog ? `-${dateTimeForFilePath()}` : '';
}
const logsFolderPath = path.resolve(process.cwd(), 'logs');
export function getDebugAdapterLogFilePath(testTitle: string): string {
return logFilePath(testTitle, 'DA');
}
/**
* Transforms a title to an equivalent title that can be used as a filename (We use this to convert the name of our tests into the name of the logfile for that test)
*/
function sanitizeTestTitle(testTitle: string) {
return testTitle
.replace(/[:\/\\]/g, '-')
// These replacements are needed for the hit count breakpoint tests, which have these characters in their title
.replace(/ > /g, ' bigger than ')
.replace(/ < /g, ' smaller than ')
.replace(/ >= /g, ' bigger than or equal to ')
.replace(/ <= /g, ' smaller than or equal to ');
}
function logFilePath(testTitle: string, logType: string) {
return path.join(logsFolderPath, `${process.platform}-${sanitizeTestTitle(testTitle)}-${logType}${dateTimeForFilePathIfNeeded()}.log`);
}
logger.init(() => { });
// Dispose the logger on unhandled errors, so it'll flush the remaining contents of the log...
process.on('uncaughtException', () => logger.dispose());
process.on('unhandledRejection', () => logger.dispose());
let currentTestTitle = '';
export function setTestLogName(testTitle: string): void {
// We call setTestLogName in the common setup code. We want to call it earlier in puppeteer tests to get the logs even when the setup fails
// So we write this code to be able to call it two times, and the second time will get ignored
if (testTitle !== currentTestTitle) {
logger.setup(LogLevel.Verbose, logFilePath(testTitle, 'TEST'));
testTitle = currentTestTitle;
}
}
class PuppeteerMethodsCalledLoggerConfiguration implements IMethodsCalledLoggerConfiguration {
private readonly _wrapped = new MethodsCalledLoggerConfiguration('', []);
public readonly replacements: ReplacementInstruction[] = [];
public customizeResult<T>(methodName: string | symbol | number, args: any, result: T): T {
if (methodName === 'waitForSelector' && typeof result === 'object' && args.length >= 1) {
return wrapWithMethodLogger(<T & object>result, args[0]);
} else {
return result;
}
}
public customizeArgumentsBeforeCall(receiverName: string, methodName: string | symbol | number, args: object[]): void {
this._wrapped.customizeArgumentsBeforeCall(receiverName, methodName, args);
}
}
export function logCallsTo<T extends object>(object: T, name: string): T {
return new MethodsCalledLogger(new PuppeteerMethodsCalledLoggerConfiguration(), object, name).wrapped();
}

Просмотреть файл

@ -2,4 +2,4 @@ export async function asyncRepeatSerially(howManyTimes: number, action: () => Pr
for (let index = 0; index < howManyTimes; ++index) {
await action();
}
}
}

Просмотреть файл

@ -1,13 +1,20 @@
import * as _ from 'lodash';
import { utils } from 'vscode-chrome-debug-core';
export async function waitUntilReadyWithTimeout(isReady: () => boolean, maxWaitTimeInMilliseconds = 5000) {
// The VSTS agents run slower than our machines. Use this value to reduce proportinoally the timeouts in your dev machine
export const DefaultTimeoutMultiplier = parseFloat(_.defaultTo(process.env['TEST_TIMEOUT_MULTIPLIER'], '1'));
/**
* Wait until the isReady condition evaluates to true. This method will evaluate it every 50 milliseconds until it returns true. It will time-out after maxWaitTimeInMilliseconds milliseconds
*/
export async function waitUntilReadyWithTimeout(isReady: () => boolean, maxWaitTimeInMilliseconds = DefaultTimeoutMultiplier * 30000 /* 30 seconds */) {
const maximumDateTimeToWaitUntil = Date.now() + maxWaitTimeInMilliseconds;
while (!isReady() && Date.now() < maximumDateTimeToWaitUntil) {
await utils.promiseTimeout(null, 50);
await utils.promiseTimeout(undefined, 10 /*ms*/);
}
if (!isReady()) {
throw new Error(`Timed-out after waiting for condition to be ready for ${maxWaitTimeInMilliseconds}ms`);
throw new Error(`Timed-out after waiting for condition to be ready for ${maxWaitTimeInMilliseconds}ms. Condition: ${isReady}`);
}
}

Просмотреть файл

@ -3,13 +3,14 @@ import { IBPActionWhenHit } from '../../core-v2/chrome/internal/breakpoints/bpAc
import { InternalFileBreakpointsWizard } from './implementation/internalFileBreakpointsWizard';
import { RemoveProperty } from '../../core-v2/typeUtils';
import { DebugProtocol } from 'vscode-debugprotocol';
import { IVerifications } from './implementation/breakpointsAssertions';
export class BreakpointWizard {
private _state: IBreakpointSetOrUnsetState = new BreakpointUnsetState(this, this._internal, this.changeStateFunction());
public constructor(
private readonly _internal: InternalFileBreakpointsWizard, public readonly position: Position,
public readonly actionWhenHit: IBPActionWhenHit, public readonly name: string) { }
public readonly actionWhenHit: IBPActionWhenHit, public readonly name: string, public readonly boundPosition: Position) { }
public async setThenWaitForVerifiedThenValidate(): Promise<BreakpointWizard> {
await this.setWithoutVerifying();
@ -33,8 +34,13 @@ export class BreakpointWizard {
return this;
}
public async assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<BreakpointWizard> {
await this._state.assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit, expectedStackTrace);
public async assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications = {}): Promise<BreakpointWizard> {
await this._state.assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit, verifications);
return this;
}
public async assertIsHitThenResume(verifications: IVerifications) {
await this._state.assertIsHitThenResume(verifications);
return this;
}
@ -59,7 +65,8 @@ export type ChangeBreakpointWizardState = (newState: IBreakpointSetOrUnsetState)
export interface IBreakpointSetOrUnsetState {
set(): Promise<void>;
unset(): Promise<void>;
assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void>;
assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void>;
assertIsHitThenResume(verifications: IVerifications): Promise<void>;
assertIsVerified(): void;
waitUntilVerified(): Promise<void>;
}
@ -80,8 +87,23 @@ class BreakpointSetState implements IBreakpointSetOrUnsetState {
this._changeState(new BreakpointUnsetState(this._breakpoint, this._internal, this._changeState));
}
public assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void> {
return this._internal.assertIsHitThenResumeWhen(this._breakpoint, lastActionToMakeBreakpointHit, expectedStackTrace);
/**
* This method is intended to avoid hangs when performing a puppeteer action that will get blocked while the debuggee hits a breakpoint.
*
* The method will execute the puppeteer action, verify that the breakpoint is hit, and afterwards verify that the puppeteer action was properly finished.
*
* More details:
* The method will also verify that the pause was in the exact locatio that the breakpoint is located, and any other verifications specified in the verifications parameter
*/
public assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void> {
return this._internal.assertIsHitThenResumeWhen(this._breakpoint, lastActionToMakeBreakpointHit, verifications);
}
/**
* Verify that the debuggee is paused due to this breakpoint, and perform a customizable list of extra verifications
*/
public assertIsHitThenResume(verifications: IVerifications): Promise<void> {
return this._internal.assertIsHitThenResume(this._breakpoint, verifications);
}
public assertIsVerified(): void {
@ -109,7 +131,11 @@ export class BreakpointUnsetState implements IBreakpointSetOrUnsetState {
throw new Error(`Can't unset a breakpoint that is already unset`);
}
public assertIsHitThenResumeWhen(_lastActionToMakeBreakpointHit: () => Promise<void>, _expectedStackTrace: string): Promise<void> {
public assertIsHitThenResumeWhen(_lastActionToMakeBreakpointHit: () => Promise<void>, _verifications: IVerifications): Promise<void> {
throw new Error(`Can't expect to hit a breakpoint that is unset`);
}
public assertIsHitThenResume(_verifications: IVerifications): Promise<void> {
throw new Error(`Can't expect to hit a breakpoint that is unset`);
}

Просмотреть файл

@ -1,23 +1,24 @@
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
import { TestProjectSpec } from '../../framework/frameworkTestSupport';
import { InternalFileBreakpointsWizard } from './implementation/internalFileBreakpointsWizard';
import { InternalFileBreakpointsWizard, BreakpointStatusChangedWithId } from './implementation/internalFileBreakpointsWizard';
import { DebugProtocol } from 'vscode-debugprotocol';
import { ValidatedMap } from '../../core-v2/chrome/collections/validatedMap';
import { wrapWithMethodLogger } from '../../core-v2/chrome/logging/methodsCalledLogger';
import assert = require('assert');
import { FileBreakpointsWizard } from './fileBreakpointsWizard';
import { waitUntilReadyWithTimeout } from '../../utils/waitUntilReadyWithTimeout';
import { isThisV2, isThisV1 } from '../../testSetup';
import { BreakpointWizard } from './breakpointWizard';
import { expect } from 'chai';
import { PausedWizard } from '../pausedWizard';
export class BreakpointsWizard {
private _state: IEventForConsumptionAvailabilityState = new NoEventAvailableToBeConsumed(this.changeStateFunction());
private readonly _pausedWizard = PausedWizard.forClient(this._client);
private readonly _pathToFileWizard = new ValidatedMap<string, InternalFileBreakpointsWizard>();
private constructor(private readonly _client: ExtendedDebugClient, private readonly _project: TestProjectSpec) {
this._client.on('stopped', stopped => this._state.onPaused(stopped));
this._client.on('continued', continued => this._state.onResumed(continued));
this._client.on('breakpoint', breakpointStatusChange => this.onBreakpointStatusChange(breakpointStatusChange));
this._client.on('breakpoint', breakpointStatusChange => this.onBreakpointStatusChange(breakpointStatusChange.body));
}
public get project() {
return this._project;
}
public static create(debugClient: ExtendedDebugClient, testProjectSpecification: TestProjectSpec): BreakpointsWizard {
@ -26,118 +27,46 @@ export class BreakpointsWizard {
public at(filePath: string): FileBreakpointsWizard {
return wrapWithMethodLogger(new FileBreakpointsWizard(this._pathToFileWizard.getOrAdd(filePath,
() => new InternalFileBreakpointsWizard(this, this._client, this._project.src(filePath)))));
() => new InternalFileBreakpointsWizard(wrapWithMethodLogger(this), this._client, this._project.src(filePath)))));
}
public async assertNotPaused(): Promise<void> {
await this._state.assertNotPaused();
public async waitAndConsumePausedEvent(_breakpoint: BreakpointWizard): Promise<void> {
// TODO: Should we validate the stack trace is on breakpoint here?
await this._pausedWizard.waitAndConsumePausedEvent(pausedInfo => {
expect(pausedInfo.reason).to.equal('breakpoint');
});
}
public assertIsPaused(): void {
this._state.assertIsPaused();
/**
* Instruct the debuggee to resume, and verify that the Debug-Adapter sends the proper notification after that happens
*/
public async resume(): Promise<void> {
return this._pausedWizard.resume();
}
public async waitUntilJustResumed(): Promise<void> {
await waitUntilReadyWithTimeout(() => this._state instanceof EventAvailableToBeConsumed);
public async waitAndConsumeResumedEvent(): Promise<void> {
return this._pausedWizard.waitAndConsumeResumedEvent();
}
await this._state.waitUntilJustResumed();
public async waitAndAssertNoMoreEvents(): Promise<void> {
return this._pausedWizard.waitAndAssertNoMoreEvents();
}
public toString(): string {
return 'Breakpoints';
}
private onBreakpointStatusChange(breakpointStatusChanged: DebugProtocol.BreakpointEvent): void {
for (const fileWizard of this._pathToFileWizard.values()) {
fileWizard.onBreakpointStatusChange(breakpointStatusChanged);
private onBreakpointStatusChange(breakpointStatusChanged: DebugProtocol.BreakpointEvent['body']): void {
if (this.isBreakpointStatusChangedWithId(breakpointStatusChanged)) {
// TODO: Update this code to only send the breakpoint to the file that owns it
for (const fileWizard of this._pathToFileWizard.values()) {
fileWizard.onBreakpointStatusChange(breakpointStatusChanged);
}
}
}
private changeStateFunction(): (newState: IEventForConsumptionAvailabilityState) => void {
return newState => this._state = newState;
}
}
interface IEventForConsumptionAvailabilityState {
readonly latestEvent: DebugProtocol.StoppedEvent | DebugProtocol.ContinuedEvent;
onPaused(stopped: DebugProtocol.StoppedEvent): void;
onResumed(continued: DebugProtocol.ContinuedEvent): void;
waitUntilJustResumed(): Promise<void>;
assertIsPaused(): void;
assertNotPaused(): Promise<void>;
}
type ChangeState = (newState: IEventForConsumptionAvailabilityState) => void;
class EventAvailableToBeConsumed implements IEventForConsumptionAvailabilityState {
public constructor(private readonly _changeState: ChangeState, public readonly latestEvent: DebugProtocol.StoppedEvent | DebugProtocol.ContinuedEvent) { }
public onPaused(stopped: DebugProtocol.StoppedEvent): void {
if (isThisV1 && this.latestEvent.event === 'continued') {
this._changeState(new EventAvailableToBeConsumed(this._changeState, stopped));
} else {
throw new Error(`Expected to consume previous event: ${JSON.stringify(this.latestEvent)} before receiving a new stopped event: ${JSON.stringify(stopped)}`);
}
}
public onResumed(continued: DebugProtocol.ContinuedEvent): void {
if (isThisV2) {
throw new Error(`Expected to consume previous event: ${JSON.stringify(this.latestEvent)} before receiving a new continued event: ${JSON.stringify(continued)}`);
}
}
public async waitUntilJustResumed(): Promise<void> {
if (this.latestEvent.event === 'continued') {
this._changeState(new NoEventAvailableToBeConsumed(this._changeState));
}
}
public assertIsPaused(): void {
if (this.latestEvent.event === 'stopped') {
this._changeState(new NoEventAvailableToBeConsumed(this._changeState));
}
}
public async assertNotPaused(): Promise<void> {
expect(this.latestEvent.event, `Expected that there was not new paused event to be consumed, and that the debugger wasn't paused yet the state was: ${this}`)
.to.not.equal('stopped');
}
public toString(): string {
return `Event available to be consumed: ${JSON.stringify(this.latestEvent)}`;
}
}
class NoEventAvailableToBeConsumed implements IEventForConsumptionAvailabilityState {
public constructor(private readonly _changeState: ChangeState) { }
public get latestEvent(): never {
throw new Error(`There is no event available to be consumed`);
}
public onPaused(stopped: DebugProtocol.StoppedEvent): void {
this._changeState(new EventAvailableToBeConsumed(this._changeState, stopped));
}
public onResumed(continued: DebugProtocol.ContinuedEvent): void {
this._changeState(new EventAvailableToBeConsumed(this._changeState, continued));
}
public waitUntilJustResumed(): Promise<void> {
throw new Error(`There is no event available to be consumed`);
}
public assertIsPaused(): void {
throw new Error(`There is no event available to be consumed`);
}
public async assertNotPaused(): Promise<void> {
// Always true for this state
}
public toString(): string {
return `NoEventAvailableToBeConsumed`;
private isBreakpointStatusChangedWithId(statusChanged: DebugProtocol.BreakpointEvent['body']): statusChanged is BreakpointStatusChangedWithId {
return statusChanged.breakpoint.id !== undefined;
}
}

Просмотреть файл

@ -2,9 +2,11 @@ import { BreakpointWizard } from './breakpointWizard';
import { InternalFileBreakpointsWizard } from './implementation/internalFileBreakpointsWizard';
import { PromiseOrNot } from 'vscode-chrome-debug-core';
import { wrapWithMethodLogger } from '../../core-v2/chrome/logging/methodsCalledLogger';
import { PauseOnHitCount } from '../../core-v2/chrome/internal/breakpoints/bpActionWhenHit';
export interface IBreakpointOptions {
lineText: string;
text: string;
boundText?: string;
}
export interface IHitCountBreakpointOptions extends IBreakpointOptions {
@ -14,15 +16,26 @@ export interface IHitCountBreakpointOptions extends IBreakpointOptions {
export class FileBreakpointsWizard {
public constructor(private readonly _internal: InternalFileBreakpointsWizard) { }
public async breakpoint(options: IBreakpointOptions): Promise<BreakpointWizard> {
const wrappedBreakpoint = wrapWithMethodLogger(await this._internal.breakpoint({
text: options.text,
boundText: options.boundText,
name: `BP @ ${options.text}`
}));
return wrappedBreakpoint.setThenWaitForVerifiedThenValidate();
}
public async hitCountBreakpoint(options: IHitCountBreakpointOptions): Promise<BreakpointWizard> {
return (await (await this.unsetHitCountBreakpoint(options)).setThenWaitForVerifiedThenValidate());
}
public async unsetHitCountBreakpoint(options: IHitCountBreakpointOptions): Promise<BreakpointWizard> {
return wrapWithMethodLogger(await this._internal.hitCountBreakpoint({
lineText: options.lineText,
hitCountCondition: options.hitCountCondition,
name: `BP @ ${options.lineText}`
return wrapWithMethodLogger(await this._internal.breakpoint({
text: options.text,
boundText: options.boundText,
actionWhenHit: new PauseOnHitCount(options.hitCountCondition),
name: `BP @ ${options.text}`
}));
}

Просмотреть файл

@ -1,8 +1,12 @@
import { DebugProtocol } from 'vscode-debugprotocol';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { BreakpointWizard } from '../breakpointWizard';
import { ValidatedMap } from '../../../core-v2/chrome/collections/validatedMap';
import { IBreakpointsBatchingStrategy, InternalFileBreakpointsWizard, CurrentBreakpointsMapping, BreakpointsUpdate } from './internalFileBreakpointsWizard';
import { BreakpointsAssertions } from './breakpointsAssertions';
import { IBreakpointsBatchingStrategy, InternalFileBreakpointsWizard, CurrentBreakpointsMapping, BreakpointsUpdate, BreakpointStatusChangedWithId } from './internalFileBreakpointsWizard';
import { BreakpointsAssertions, IVerifications } from './breakpointsAssertions';
import { BreakpointsWizard } from '../breakpointsWizard';
export class PerformChangesImmediatelyState implements IBreakpointsBatchingStrategy {
@ -35,9 +39,9 @@ export class PerformChangesImmediatelyState implements IBreakpointsBatchingStrat
await this._internal.sendBreakpointsToClient(new BreakpointsUpdate([], [breakpointWizard], remainingBreakpoints));
}
public onBreakpointStatusChange(breakpointStatusChanged: DebugProtocol.BreakpointEvent): void {
const breakpoint = this._idToBreakpoint.get(breakpointStatusChanged.body.breakpoint.id);
this.currentBreakpointsMapping.setAndReplaceIfExist(breakpoint, breakpointStatusChanged.body.breakpoint);
public onBreakpointStatusChange(breakpointStatusChanged: BreakpointStatusChangedWithId): void {
const breakpoint = this._idToBreakpoint.get(breakpointStatusChanged.breakpoint.id);
this.currentBreakpointsMapping.setAndReplaceIfExist(breakpoint, breakpointStatusChanged.breakpoint);
}
public assertIsVerified(breakpoint: BreakpointWizard): void {
@ -48,8 +52,12 @@ export class PerformChangesImmediatelyState implements IBreakpointsBatchingStrat
await this._breakpointsAssertions.waitUntilVerified(breakpoint);
}
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void> {
await this._breakpointsAssertions.assertIsHitThenResumeWhen(breakpoint, lastActionToMakeBreakpointHit, expectedStackTrace);
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void> {
await this._breakpointsAssertions.assertIsHitThenResumeWhen(breakpoint, lastActionToMakeBreakpointHit, verifications);
}
public async assertIsHitThenResume(breakpoint: BreakpointWizard, verifications: IVerifications): Promise<void> {
await this._breakpointsAssertions.assertIsHitThenResume(breakpoint, verifications);
}
private currentBreakpoints(): BreakpointWizard[] {

Просмотреть файл

@ -1,18 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import _ = require('lodash');
import { DebugProtocol } from 'vscode-debugprotocol';
import { BreakpointWizard } from '../breakpointWizard';
import { PromiseOrNot } from 'vscode-chrome-debug-core';
import { ValidatedSet } from '../../../core-v2/chrome/collections/validatedSet';
import {
IBreakpointsBatchingStrategy, InternalFileBreakpointsWizard, CurrentBreakpointsMapping, BreakpointsUpdate
IBreakpointsBatchingStrategy, InternalFileBreakpointsWizard, CurrentBreakpointsMapping, BreakpointsUpdate, BreakpointStatusChangedWithId
} from './internalFileBreakpointsWizard';
import { IVerifications } from './breakpointsAssertions';
export class BatchingUpdatesState implements IBreakpointsBatchingStrategy {
private readonly _breakpointsToSet = new ValidatedSet<BreakpointWizard>();
private readonly _breakpointsToUnset = new ValidatedSet<BreakpointWizard>();
private readonly _actionsToCompleteAfterBatch: (() => PromiseOrNot<void>)[] = [];
public constructor(private readonly _internal: InternalFileBreakpointsWizard, public readonly currentBreakpointsMapping: CurrentBreakpointsMapping) { }
public constructor(private readonly _internal: InternalFileBreakpointsWizard, public readonly currentBreakpointsMapping: CurrentBreakpointsMapping) {}
public set(breakpointWizard: BreakpointWizard): void {
this._breakpointsToSet.add(breakpointWizard);
@ -32,11 +37,15 @@ export class BatchingUpdatesState implements IBreakpointsBatchingStrategy {
this._actionsToCompleteAfterBatch.push(() => this._internal.waitUntilVerified(breakpoint));
}
public onBreakpointStatusChange(_breakpointStatusChanged: DebugProtocol.BreakpointEvent): void {
public onBreakpointStatusChange(_breakpointStatusChanged: BreakpointStatusChangedWithId): void {
throw new Error(`Breakpoint status shouldn't be updated while doing a batch update. Is this happening due to a product or test bug?`);
}
public async assertIsHitThenResumeWhen(_breakpoint: BreakpointWizard, _lastActionToMakeBreakpointHit: () => Promise<void>, _expectedStackTrace: string): Promise<void> {
public async assertIsHitThenResumeWhen(_breakpoint: BreakpointWizard, _lastActionToMakeBreakpointHit: () => Promise<void>, _verifications: IVerifications): Promise<void> {
throw new Error(`Breakpoint shouldn't be verified while doing a batch update. Is this happening due to a product or test bug?`);
}
public async assertIsHitThenResume(_breakpoint: BreakpointWizard, _verifications: IVerifications): Promise<void> {
throw new Error(`Breakpoint shouldn't be verified while doing a batch update. Is this happening due to a product or test bug?`);
}

Просмотреть файл

@ -1,94 +1,98 @@
import * as assert from 'assert';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { expect, use } from 'chai';
import * as chaiString from 'chai-string';
import { DebugProtocol } from 'vscode-debugprotocol';
import { THREAD_ID } from 'vscode-chrome-debug-core-testsupport';
import { BreakpointWizard } from '../breakpointWizard';
import { InternalFileBreakpointsWizard, CurrentBreakpointsMapping } from './internalFileBreakpointsWizard';
import { BreakpointsWizard } from '../breakpointsWizard';
import { waitUntilReadyWithTimeout } from '../../../utils/waitUntilReadyWithTimeout';
import { isThisV2, isThisV1 } from '../../../testSetup';
import { ExpectedFrame, StackTraceObjectAssertions } from './stackTraceObjectAssertions';
import { StackTraceStringAssertions } from './stackTraceStringAssertions';
import { VariablesWizard, IExpectedVariables } from '../../variables/variablesWizard';
import { StackFrameWizard, stackTrace } from '../../variables/stackFrameWizard';
use(chaiString);
export interface IVerifications {
variables?: IExpectedVariables;
stackTrace?: string | ExpectedFrame[];
stackFrameFormat?: DebugProtocol.StackFrameFormat;
}
interface IObjectWithLocation {
source?: DebugProtocol.Source;
line?: number; // One based line number
column?: number; // One based colum number
}
export class BreakpointsAssertions {
private readonly _variablesWizard = new VariablesWizard(this._internal.client);
public constructor(
private readonly _breakpointsWizard: BreakpointsWizard,
private readonly _internal: InternalFileBreakpointsWizard,
public readonly currentBreakpointsMapping: CurrentBreakpointsMapping) { }
public assertIsVerified(breakpoint: BreakpointWizard): void {
const breakpointStatus = this.currentBreakpointsMapping.get(breakpoint);
assert(breakpointStatus.verified, `Expected ${breakpoint} to be verified yet it wasn't: ${breakpointStatus.message}`);
// Convert to one based to match the VS Code potocol and what VS Code does if you try to open that file at that line number
// const oneBasedExpectedLineNumber = breakpoint.position.lineNumber + 1;
// const oneBasedExpectedColumnNumber = breakpoint.position.columnNumber + 1;
// const filePath = this._internal.filePath;
// TODO: Re-enable this once we figure out how to deal with source-maps that do unexpected things
// assert.equal(breakpointStatus.line, oneBasedExpectedLineNumber,
// `Expected ${breakpoint} actual line to be ${filePath}:${oneBasedExpectedLineNumber}:${oneBasedExpectedColumnNumber}`
// + ` yet it was ${filePath}:${breakpointStatus.line}:${breakpointStatus.column}`);
const breakpointStatus = this.currentBreakpointsMapping.get(breakpoint);
this.assertLocationMatchesExpected(breakpointStatus, breakpoint);
expect(breakpointStatus.verified, `Expected ${breakpoint} to be verified yet it wasn't: ${breakpointStatus.message}`).to.equal(true);
}
public async waitUntilVerified(breakpoint: BreakpointWizard): Promise<void> {
await waitUntilReadyWithTimeout(() => this.currentBreakpointsMapping.get(breakpoint).verified);
}
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void> {
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void> {
const actionResult = lastActionToMakeBreakpointHit();
const vsCodeStatus = this.currentBreakpointsMapping.get(breakpoint);
const location = { path: this._internal.filePath, line: vsCodeStatus.line, colum: vsCodeStatus.column };
// TODO: Merge the two following calls together
await this._internal.client.assertStoppedLocation('breakpoint', location);
await this._breakpointsWizard.assertIsPaused();
const stackTraceResponse = await this._internal.client.send('stackTrace', {
threadId: THREAD_ID,
format: {
parameters: true,
parameterTypes: true,
parameterNames: true,
line: true,
module: true
}
});
this.validateStackTraceResponse(stackTraceResponse);
const formattedExpectedStackTrace = expectedStackTrace.replace(/^\s+/gm, ''); // Remove the white space we put at the start of the lines to make the stack trace align with the code
const actualStackTrace = this.extractStackTrace(stackTraceResponse);
assert.equal(actualStackTrace, formattedExpectedStackTrace, `Expected the stack trace when hitting ${breakpoint} to be:\n${formattedExpectedStackTrace}\nyet it is:\n${actualStackTrace}`);
// const scopesResponse = await this._internal.client.scopesRequest({ frameId: stackTraceResponse.body.stackFrames[0].id });
/// const scopes = scopesResponse.body.scopes;
await this._internal.client.continueRequest();
if (isThisV2) {
// TODO: Is getting this event on V2 a bug? See: Continued Event at https://microsoft.github.io/debug-adapter-protocol/specification
await this._breakpointsWizard.waitUntilJustResumed();
}
await this.assertIsHitThenResume(breakpoint, verifications);
await actionResult;
}
private extractStackTrace(stackTraceResponse: DebugProtocol.StackTraceResponse): string {
return stackTraceResponse.body.stackFrames.map(f => this.printStackTraceFrame(f)).join('\n');
public async assertIsHitThenResume(breakpoint: BreakpointWizard, verifications: IVerifications): Promise<void> {
await this._breakpointsWizard.waitAndConsumePausedEvent(breakpoint);
const stackTraceFrames = (await stackTrace(this._internal.client, verifications.stackFrameFormat)).stackFrames;
// Validate that the topFrame is locate in the same place as the breakpoint
this.assertLocationMatchesExpected(stackTraceFrames[0], breakpoint);
if (typeof verifications.stackTrace === 'string') {
const assertions = new StackTraceStringAssertions(breakpoint);
assertions.assertResponseMatches(stackTraceFrames, verifications.stackTrace);
} else if (typeof verifications.stackTrace === 'object') {
const assertions = new StackTraceObjectAssertions(this._breakpointsWizard);
assertions.assertResponseMatches(stackTraceFrames, verifications.stackTrace);
}
if (verifications.variables !== undefined) {
await this._variablesWizard.assertStackFrameVariablesAre(new StackFrameWizard(this._internal.client, stackTraceFrames[0]), verifications.variables);
}
await this._breakpointsWizard.resume();
}
private printStackTraceFrame(frame: DebugProtocol.StackFrame): string {
let frameName = frame.name;
return `${frameName}:${frame.column}${frame.presentationHint && frame.presentationHint !== 'normal' ? ` (${frame.presentationHint})` : ''}`;
}
private assertLocationMatchesExpected(objectWithLocation: IObjectWithLocation, breakpoint: BreakpointWizard): void {
const expectedFilePath = this._internal.filePath;
private validateStackTraceResponse(stackTraceResponse: DebugProtocol.StackTraceResponse) {
expect(stackTraceResponse.success, `Expected the response to the stack trace request to be succesful yet it failed: ${JSON.stringify(stackTraceResponse)}`).to.equal(true);
expect(stackTraceResponse.body.totalFrames, `The number of stackFrames was different than the value supplied on the totalFrames field`)
.to.equal(stackTraceResponse.body.stackFrames.length);
stackTraceResponse.body.stackFrames.forEach(frame => {
// Warning: We don't currently validate frame.source.path
const expectedSourceNameAndLine = ` [${frame.source.name}] Line ${frame.line}`;
expect(frame.name, 'Expected the formatted name to match the source name and line supplied as individual attributes').to.endsWith(expectedSourceNameAndLine);
});
expect(objectWithLocation.source).to.not.equal(undefined);
expect(objectWithLocation.source!.path!.toLowerCase()).to.be.equal(expectedFilePath.toLowerCase());
expect(objectWithLocation.source!.name!.toLowerCase()).to.be.equal(path.basename(expectedFilePath.toLowerCase()));
const expectedLineNumber = breakpoint.boundPosition.lineNumber + 1;
const expectedColumNumber = breakpoint.boundPosition.columnNumber + 1;
const expectedBPLocationPrinted = `${expectedFilePath}:${expectedLineNumber}:${expectedColumNumber}`;
const actualBPLocationPrinted = `${objectWithLocation.source!.path}:${objectWithLocation.line}:${objectWithLocation.column}`;
expect(actualBPLocationPrinted.toLowerCase()).to.be.equal(expectedBPLocationPrinted.toLowerCase());
}
}

Просмотреть файл

@ -1,12 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as _ from 'lodash';
import { DebugProtocol } from 'vscode-debugprotocol';
import { BreakpointsUpdate, StateChanger, InternalFileBreakpointsWizard } from './internalFileBreakpointsWizard';
import { BreakpointsUpdate, StateChanger, InternalFileBreakpointsWizard, BreakpointWithId } from './internalFileBreakpointsWizard';
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
import { BreakpointWizard, VSCodeActionWhenHit } from '../breakpointWizard';
import { PerformChangesImmediatelyState } from './performChangesImmediatelyState';
import { ValidatedMap } from '../../../core-v2/chrome/collections/validatedMap';
import { PauseOnHitCount } from '../../../core-v2/chrome/internal/breakpoints/bpActionWhenHit';
import { PauseOnHitCount, AlwaysPause } from '../../../core-v2/chrome/internal/breakpoints/bpActionWhenHit';
import { BreakpointsWizard } from '../breakpointsWizard';
import { Replace } from '../../../core-v2/typeUtils';
type SetBreakpointsResponseWithId = Replace<DebugProtocol.SetBreakpointsResponse, 'body',
Replace<DebugProtocol.SetBreakpointsResponse['body'], 'breakpoints', BreakpointWithId[]>>;
export class BreakpointsUpdater {
public constructor(
@ -21,17 +30,11 @@ export class BreakpointsUpdater {
const response = await this._client.setBreakpointsRequest({ breakpoints: vsCodeBps, source: { path: this._internal.filePath } });
if (!response.success) {
throw new Error(`Failed to set the breakpoints for: ${this._internal.filePath}`);
}
this.validateResponse(response, vsCodeBps);
const responseWithIds = <SetBreakpointsResponseWithId>response;
const expected = vsCodeBps.length;
const actual = response.body.breakpoints.length;
if (actual !== expected) {
throw new Error(`Expected to receive ${expected} breakpoints yet we got ${actual}. Received breakpoints: ${JSON.stringify(response.body.breakpoints)}`);
}
const breakpointToStatus = new ValidatedMap<BreakpointWizard, DebugProtocol.Breakpoint>(_.zip(updatedBreakpoints, response.body.breakpoints));
const breakpointToStatus = new ValidatedMap<BreakpointWizard, BreakpointWithId>
(<[[BreakpointWizard, BreakpointWithId]]>_.zip(updatedBreakpoints, responseWithIds.body.breakpoints));
this._changeState(new PerformChangesImmediatelyState(this._breakpointsWizard, this._internal, breakpointToStatus));
}
@ -43,10 +46,29 @@ export class BreakpointsUpdater {
}
private actionWhenHitToVSCodeProtocol(breakpoint: BreakpointWizard): VSCodeActionWhenHit {
if (breakpoint.actionWhenHit instanceof PauseOnHitCount) {
if (breakpoint.actionWhenHit instanceof AlwaysPause) {
return {};
} else if (breakpoint.actionWhenHit instanceof PauseOnHitCount) {
return { hitCondition: breakpoint.actionWhenHit.pauseOnHitCondition };
} else {
throw new Error('Not yet implemented');
}
}
private validateResponse(response: DebugProtocol.SetBreakpointsResponse, vsCodeBps: DebugProtocol.SourceBreakpoint[]): void {
if (!response.success) {
throw new Error(`Failed to set the breakpoints for: ${this._internal.filePath}`);
}
const expected = vsCodeBps.length;
const actual = response.body.breakpoints.length;
if (actual !== expected) {
throw new Error(`Expected to receive ${expected} breakpoints yet we got ${actual}. Received breakpoints: ${JSON.stringify(response.body.breakpoints)}`);
}
const bpsWithoutId = response.body.breakpoints.filter(bp => bp.id === undefined);
if (bpsWithoutId.length !== 0) {
throw new Error(`Expected to receive all breakpoints with id yet we got some without ${JSON.stringify(response.body.breakpoints)}`);
}
}
}

Просмотреть файл

@ -1,7 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
import { findPositionOfTextInFile } from '../../../utils/findPositionOfTextInFile';
import { DebugProtocol } from 'vscode-debugprotocol';
import { PauseOnHitCount } from '../../../core-v2/chrome/internal/breakpoints/bpActionWhenHit';
import { AlwaysPause, IBPActionWhenHit } from '../../../core-v2/chrome/internal/breakpoints/bpActionWhenHit';
import { BreakpointWizard } from '../breakpointWizard';
import { ValidatedMap } from '../../../core-v2/chrome/collections/validatedMap';
import { FileBreakpointsWizard } from '../fileBreakpointsWizard';
@ -10,6 +15,11 @@ import { BatchingUpdatesState } from './batchingUpdatesState';
import { PerformChangesImmediatelyState } from './performChangesImmediatelyState';
import { BreakpointsUpdater } from './breakpointsUpdater';
import { BreakpointsWizard } from '../breakpointsWizard';
import { MakePropertyRequired, Replace } from '../../../core-v2/typeUtils';
import { IVerifications } from './breakpointsAssertions';
export type BreakpointWithId = MakePropertyRequired<DebugProtocol.Breakpoint, 'id'>;
export type BreakpointStatusChangedWithId = Replace<DebugProtocol.BreakpointEvent['body'], 'breakpoint', BreakpointWithId>;
export class BreakpointsUpdate {
public constructor(
@ -26,12 +36,13 @@ export interface IBreakpointsBatchingStrategy {
waitUntilVerified(breakpoint: BreakpointWizard): Promise<void>;
assertIsVerified(breakpoint: BreakpointWizard): void;
assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void>;
assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void>;
assertIsHitThenResume(breakpoint: BreakpointWizard, verifications: IVerifications): Promise<void>;
onBreakpointStatusChange(breakpointStatusChanged: DebugProtocol.BreakpointEvent): void;
onBreakpointStatusChange(breakpointStatusChanged: BreakpointStatusChangedWithId): void;
}
export type CurrentBreakpointsMapping = ValidatedMap<BreakpointWizard, DebugProtocol.Breakpoint>;
export type CurrentBreakpointsMapping = ValidatedMap<BreakpointWizard, BreakpointWithId>;
export type StateChanger = (newState: IBreakpointsBatchingStrategy) => void;
@ -42,9 +53,12 @@ export class InternalFileBreakpointsWizard {
public constructor(private readonly _breakpointsWizard: BreakpointsWizard, public readonly client: ExtendedDebugClient, public readonly filePath: string) { }
public async hitCountBreakpoint(options: { lineText: string; hitCountCondition: string; name: string }): Promise<BreakpointWizard> {
const position = await findPositionOfTextInFile(this.filePath, options.lineText);
return new BreakpointWizard(this, position, new PauseOnHitCount(options.hitCountCondition), options.name);
public async breakpoint(options: { name: string, text: string, boundText?: string, actionWhenHit?: IBPActionWhenHit}) {
const position = await findPositionOfTextInFile(this.filePath, options.text);
const boundPosition = options.boundText ? await findPositionOfTextInFile(this.filePath, options.boundText) : position;
const actionWhenHit = options.actionWhenHit || new AlwaysPause();
return new BreakpointWizard(this, position, actionWhenHit, options.name, boundPosition);
}
public async set(breakpointWizard: BreakpointWizard): Promise<void> {
@ -63,11 +77,15 @@ export class InternalFileBreakpointsWizard {
this._state.assertIsVerified(breakpoint);
}
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void> {
return this._state.assertIsHitThenResumeWhen(breakpoint, lastActionToMakeBreakpointHit, expectedStackTrace);
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void> {
return this._state.assertIsHitThenResumeWhen(breakpoint, lastActionToMakeBreakpointHit, verifications);
}
public onBreakpointStatusChange(breakpointStatusChanged: DebugProtocol.BreakpointEvent): void {
public async assertIsHitThenResume(breakpoint: BreakpointWizard, verifications: IVerifications): Promise<void> {
return this._state.assertIsHitThenResume(breakpoint, verifications);
}
public onBreakpointStatusChange(breakpointStatusChanged: BreakpointStatusChangedWithId): void {
this._state.onBreakpointStatusChange(breakpointStatusChanged);
}

Просмотреть файл

@ -0,0 +1,8 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
/** Remove the whitespaces from the start of each line and any comments we find at the end */
export function trimWhitespaceAndComments(printedTestInput: string): string {
return printedTestInput.replace(/^\s+/gm, '').replace(/ ?\/\/.*$/gm, ''); // Remove the white space we put at the start of the lines to make the printed test input align with the code
}

Просмотреть файл

@ -0,0 +1,115 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as path from 'path';
import * as testSetup from '../../../testSetup';
import { expect } from 'chai';
import { DebugProtocol } from 'vscode-debugprotocol';
import { BreakpointsWizard } from '../breakpointsWizard';
import { URL } from 'url';
export interface ExpectedSource {
fileRelativePath?: string;
url?: URL;
evalCode?: boolean;
}
export interface ExpectedFrame {
name: string | RegExp;
line?: number;
column?: number;
source?: ExpectedSource;
presentationHint?: string;
}
export class StackTraceObjectAssertions {
private readonly _projectRoot: string;
public constructor(breakpointsWizard: BreakpointsWizard) {
this._projectRoot = breakpointsWizard.project.props.projectRoot;
}
private assertSourceMatches(actual: DebugProtocol.Source | undefined, expected: ExpectedSource | undefined, index: number) {
if (actual == null && expected == null) {
return;
}
if (expected == null) {
assert.fail(`Source was returned for frame ${index} but none was expected`);
return;
}
if (actual == null) {
assert.fail(`Source was expected for frame ${index} but none was returned`);
return;
}
let expectedName: string;
let expectedPath: string;
if (expected.fileRelativePath) {
// Generate the expected path from the relative path and the project root
expectedPath = path.join(this._projectRoot, expected.fileRelativePath);
expectedName = path.parse(expectedPath).base;
} else if (expected.url) {
expectedName = expected.url.host;
expectedPath = expected.url.toString();
} else if (expected.evalCode === true) {
// Eval code has source that looks like 'VM123'. Check it by regex instead.
expect(actual.name).to.match(/.*VM.*/, `Frame ${index} source name`);
expect(actual.path).to.match(/.*VM.*/, `Frame ${index} source path`);
return;
} else {
assert.fail('Not enough information for expected source: set either "fileRelativePath" or "urlRelativePath" or "eval"');
return;
}
expect(actual.name).to.equal(expectedName, `Frame ${index} source name`);
expect(actual.path).to.equal(expectedPath, `Frame ${index} source path`);
}
private assertFrameMatches(actual: DebugProtocol.StackFrame, expected: ExpectedFrame, index: number) {
if (typeof expected.name === 'string') {
expect(actual.name).to.equal(expected.name, `Frame ${index} name`);
} else if (expected.name instanceof RegExp) {
expect(actual.name).to.match(expected.name, `Frame ${index} name`);
}
expect(actual.line).to.equal(expected.line, `Frame ${index} line`);
expect(actual.column).to.equal(expected.column, `Frame ${index} column`);
// Normal V1 stack frames will have no presentationHint, normal V2 stack frames will have presentationHint 'normal'
if (testSetup.isThisV1 && expected.presentationHint === 'normal') {
// tslint:disable-next-line:no-unused-expression
expect(actual.presentationHint, `Frame ${index} presentationHint`).to.be.undefined;
} else {
expect(actual.presentationHint).to.equal(expected.presentationHint, `Frame ${index} presentationHint`);
}
this.assertSourceMatches(actual.source, expected.source, index);
}
private assertResponseMatchesFrames(actualFrames: DebugProtocol.StackFrame[], expectedFrames: ExpectedFrame[]) {
// Check array length
expect(actualFrames.length).to.equal(expectedFrames.length, 'Number of stack frames');
// Check each frame
actualFrames.forEach((actualFrame, i) => {
this.assertFrameMatches(actualFrame, expectedFrames[i], i);
});
}
public assertResponseMatches(stackTraceFrames: DebugProtocol.StackFrame[], expectedFrames: ExpectedFrame[]) {
try {
this.assertResponseMatchesFrames(stackTraceFrames, expectedFrames);
} catch (e) {
const error: assert.AssertionError = e;
error.message += '\nActual stack trace response: \n' + JSON.stringify(stackTraceFrames, null, 2);
throw error;
}
}
}

Просмотреть файл

@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { DebugProtocol } from 'vscode-debugprotocol';
import { expect } from 'chai';
import { findLineNumber } from '../../../utils/findPositionOfTextInFile';
import { BreakpointWizard } from '../breakpointWizard';
import { trimWhitespaceAndComments } from './printedTestInputl';
export class StackTraceStringAssertions {
public constructor(
private readonly _breakpoint: BreakpointWizard) { }
public assertResponseMatches(stackTraceFrames: DebugProtocol.StackFrame[], expectedString: string) {
stackTraceFrames.forEach(frame => {
// Warning: We don't currently validate frame.source.path
expect(frame.source).not.to.equal(undefined);
const expectedSourceNameAndLine = ` [${frame.source!.name}] Line ${frame.line}`;
expect(frame.name, 'Expected the formatted name to match the source name and line supplied as individual attributes').to.endsWith(expectedSourceNameAndLine);
});
const formattedExpectedStackTrace = trimWhitespaceAndComments(expectedString);
this.applyIgnores(formattedExpectedStackTrace, stackTraceFrames);
const actualStackTrace = this.extractStackTrace(stackTraceFrames);
assert.equal(actualStackTrace, formattedExpectedStackTrace, `Expected the stack trace when hitting ${this._breakpoint} to be:\n${formattedExpectedStackTrace}\nyet it is:\n${actualStackTrace}`);
}
private applyIgnores(formattedExpectedStackTrace: string, stackTrace: DebugProtocol.StackFrame[]): void {
const ignoreFunctionNameText = '<__IGNORE_FUNCTION_NAME__>';
const ignoreFunctionName = findLineNumber(formattedExpectedStackTrace, formattedExpectedStackTrace.indexOf(ignoreFunctionNameText));
if (ignoreFunctionName >= 0) {
expect(stackTrace.length).to.be.greaterThan(ignoreFunctionName);
const ignoredFrame = stackTrace[ignoreFunctionName];
ignoredFrame.name = `${ignoreFunctionNameText} [${ignoredFrame.source!.name}] Line ${ignoredFrame.line}`;
}
}
private extractStackTrace(stackTrace: DebugProtocol.StackFrame[]): string {
return stackTrace.map(f => this.printStackTraceFrame(f)).join('\n');
}
private printStackTraceFrame(frame: DebugProtocol.StackFrame): string {
let frameName = frame.name;
return `${frameName}:${frame.column}${frame.presentationHint && frame.presentationHint !== 'normal' ? ` (${frame.presentationHint})` : ''}`;
}
}

Просмотреть файл

@ -0,0 +1,157 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DebugProtocol } from 'vscode-debugprotocol';
import { ExtendedDebugClient, THREAD_ID } from 'vscode-chrome-debug-core-testsupport';
import { logger } from 'vscode-debugadapter';
import { utils } from 'vscode-chrome-debug-core';
import { isThisV2 } from '../testSetup';
import { waitUntilReadyWithTimeout } from '../utils/waitUntilReadyWithTimeout';
import { expect } from 'chai';
import { ValidatedMap } from '../core-v2/chrome/collections/validatedMap';
import { wrapWithMethodLogger } from '../core-v2/chrome/logging/methodsCalledLogger';
enum EventToConsume {
Paused,
Resumed,
None
}
/** Helper methods to wait and/or verify when the debuggee was paused for any kind of pause.
*
* Warning: Needs to be created before the debuggee is launched to capture all events and avoid race conditions
*/
export class PausedWizard {
private _noMoreEventsExpected = false;
private _eventsToBeConsumed: (DebugProtocol.ContinuedEvent | DebugProtocol.StoppedEvent)[] = [];
private static _clientToPausedWizard = new ValidatedMap<ExtendedDebugClient, PausedWizard>();
private constructor(private readonly _client: ExtendedDebugClient) {
this._client.on('stopped', stopped => this.onEvent(stopped));
this._client.on('continued', continued => this.onEvent(continued));
}
private onEvent(continued: any) {
this.validateNoMoreEventsIfSet(continued);
this._eventsToBeConsumed.push(continued);
this.logState();
}
// The PausedWizard logic will break if we create 2 PausedWizards for the same client. So we warranty we only create one
public static forClient(client: ExtendedDebugClient): PausedWizard {
return this._clientToPausedWizard.getOrAdd(client, () => wrapWithMethodLogger(new PausedWizard(client)));
}
/**
* Verify that the debuggee is not paused
*
* @param millisecondsToWaitForPauses How much time to wait for pauses
*/
public async waitAndConsumeResumedEvent(): Promise<void> {
await waitUntilReadyWithTimeout(() => this.nextEventToConsume === EventToConsume.Resumed);
this.markNextEventAsConsumed('continued');
}
/** Return whether the debuggee is currently paused */
public isPaused(): boolean {
return this.nextEventToConsume === EventToConsume.Paused;
}
/** Wait and block until the debuggee is paused on a debugger statement */
public async waitUntilPausedOnDebuggerStatement(): Promise<void> {
return this.waitAndConsumePausedEvent(pauseInfo => {
expect(pauseInfo.description).to.equal('Paused on debugger statement');
expect(pauseInfo.reason).to.equal('debugger_statement');
});
}
/** Wait and block until the debuggee is paused, and then perform the specified action with the pause event's body */
public async waitAndConsumePausedEvent(actionWithPausedInfo: (pausedInfo: DebugProtocol.StoppedEvent['body']) => void): Promise<void> {
await waitUntilReadyWithTimeout(() => this.nextEventToConsume === EventToConsume.Paused);
const pausedEvent = <DebugProtocol.StoppedEvent>this._eventsToBeConsumed[0];
this.markNextEventAsConsumed('stopped');
actionWithPausedInfo(pausedEvent.body);
}
/** Wait and block until the debuggee has been resumed */
public async waitUntilResumed(): Promise<void> {
// We assume that nobody is consuming events in parallel, so if we start paused, the wait call won't ever succeed
expect(this.nextEventToConsume).to.not.equal(EventToConsume.Paused);
await waitUntilReadyWithTimeout(() => this.nextEventToConsume === EventToConsume.Resumed);
this.markNextEventAsConsumed('continued');
}
/**
* Instruct the debuggee to resume, and verify that the Debug-Adapter sends the proper notification after that happens
*/
public async resume(): Promise<void> {
await this._client.continueRequest();
if (isThisV2) {
// TODO: Is getting this event on V2 a bug? See: Continued Event at https://microsoft.github.io/debug-adapter-protocol/specification
await this.waitUntilResumed();
}
}
/**
* Instruct the debuggee to pause, and verify that the Debug-Adapter sends the proper notification after that happens
*/
public async pause(): Promise<void> {
await this._client.pauseRequest({ threadId: THREAD_ID });
await this.waitAndConsumePausedEvent(event => {
expect(event.reason).to.equal('pause');
expect(event.description).to.equal('Paused on user request');
});
}
public async waitAndAssertNoMoreEvents(): Promise<void> {
expect(this.nextEventToConsume).to.equal(EventToConsume.None);
this._noMoreEventsExpected = true;
// Wait some time, to see if any events appear eventually
await utils.promiseTimeout(undefined, 500);
expect(this.nextEventToConsume).to.equal(EventToConsume.None);
}
private validateNoMoreEventsIfSet(event: DebugProtocol.ContinuedEvent | DebugProtocol.StoppedEvent): void {
if (this._noMoreEventsExpected) {
throw new Error(`Received an event after it was signaled that no more events were expected: ${JSON.stringify(event)}`);
}
}
private logState() {
logger.log(`Resume/Pause #events = ${this._eventsToBeConsumed.length}, state = ${EventToConsume[this.nextEventToConsume]}`);
}
private get nextEventToConsume(): EventToConsume {
if (this._eventsToBeConsumed.length === 0) {
return EventToConsume.None;
} else {
const nextEventToBeConsumed = this._eventsToBeConsumed[0];
switch (nextEventToBeConsumed.event) {
case 'stopped':
return EventToConsume.Paused;
case 'continued':
return EventToConsume.Resumed;
default:
throw new Error(`Expected the event to be consumed to be either a stopped or continued yet it was: ${JSON.stringify(nextEventToBeConsumed)}`);
}
}
}
private markNextEventAsConsumed(eventName: 'continued' | 'stopped'): void {
expect(this._eventsToBeConsumed).length.to.be.greaterThan(0);
expect(this._eventsToBeConsumed[0].event).to.equal(eventName);
this._eventsToBeConsumed.shift();
this.logState();
}
public toString(): string {
return 'PausedWizard';
}
}

Просмотреть файл

@ -0,0 +1,2 @@
export type ManyVariablesPropertiesPrinted = string; // `${variable.name} = ${variable.value} ${(variable.type)}\n`

Просмотреть файл

@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { expect } from 'chai';
import * as _ from 'lodash';
import { DebugProtocol } from 'vscode-debugprotocol';
import { THREAD_ID, ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
import { VariablesScopeName } from './variablesWizard';
import { ValidatedSet, IValidatedSet } from '../../core-v2/chrome/collections/validatedSet';
import { singleElementOfArray } from '../../core-v2/chrome/collections/utilities';
import { logger } from 'vscode-debugadapter';
interface IVariablesOfScope {
scopeName: VariablesScopeName;
variables: DebugProtocol.Variable[];
}
const defaultStackFrameFormat: DebugProtocol.StackFrameFormat = {
parameters: true,
parameterTypes: true,
parameterNames: true,
line: true,
module: true
};
export async function stackTrace(client: ExtendedDebugClient, optionalStackFrameFormat?: DebugProtocol.StackFrameFormat): Promise<DebugProtocol.StackTraceResponse['body']> {
const stackFrameFormat = _.defaultTo(optionalStackFrameFormat, defaultStackFrameFormat);
const stackTraceResponse = await client.send('stackTrace', { threadId: THREAD_ID, format: stackFrameFormat });
expect(stackTraceResponse.success, `Expected the response to the stack trace request to be succesful yet it failed: ${JSON.stringify(stackTraceResponse)}`).to.equal(true);
// Check totalFrames property
expect(stackTraceResponse.body.totalFrames).to.equal(stackTraceResponse.body.stackFrames.length, 'body.totalFrames');
return stackTraceResponse.body;
}
export async function topStackFrame(client: ExtendedDebugClient, optionalStackFrameFormat?: DebugProtocol.StackFrameFormat): Promise<DebugProtocol.StackFrame> {
const stackFrames = (await stackTrace(client, optionalStackFrameFormat)).stackFrames;
expect(stackFrames.length).to.be.greaterThan(0);
return stackFrames[0];
}
/** Utility functions to operate on the stack straces and stack frames of the debuggee.
* It also provides utilities to access the scopes available in a particular stack frame.
*/
export class StackFrameWizard {
public constructor(private readonly _client: ExtendedDebugClient, private readonly _stackFrame: DebugProtocol.StackFrame) { }
/** Return a Wizard to interact with the top stack frame of the debuggee of the client */
public static async topStackFrame(client: ExtendedDebugClient): Promise<StackFrameWizard> {
return new StackFrameWizard(client, await topStackFrame(client));
}
/** Return the variables information for the scopes selected by name */
public async variablesOfScopes(manyScopeNames: VariablesScopeName[]): Promise<IVariablesOfScope[]> {
const scopes = await this.scopesByNames(manyScopeNames);
return Promise.all(scopes.map(async scope => {
const variablesResponse = await this._client.variablesRequest({ variablesReference: scope!.variablesReference });
expect(variablesResponse.success).to.equal(true);
expect(variablesResponse.body).not.to.equal(undefined);
const variables = variablesResponse.body.variables;
expect(variables).not.to.equal(undefined);
return { scopeName: <VariablesScopeName>scope.name.toLowerCase(), variables };
}));
}
private async scopesByNames(manyScopeNames: VariablesScopeName[]): Promise<DebugProtocol.Scope[]> {
const scopeNamesSet = new ValidatedSet(manyScopeNames.map(name => name.toLowerCase()));
const requestedScopes = (await this.scopes()).filter(scope => scopeNamesSet.has(scope.name.toLowerCase()));
expect(requestedScopes).to.have.lengthOf(manyScopeNames.length);
return requestedScopes;
}
/** Return all the scopes available in the underlying stack frame */
public async scopes(): Promise<DebugProtocol.Scope[]> {
const scopesResponse = await this._client.scopesRequest({ frameId: this._stackFrame.id });
logger.log(`Scopes: ${scopesResponse.body.scopes.map(s => s.name).join(', ')}`);
return scopesResponse.body.scopes;
}
/** Return the names of all the global variables in the underlying stack frame */
public async globalVariableNames(): Promise<IValidatedSet<string>> {
const existingGlobalVariables = await this.variablesOfScope('global');
return new ValidatedSet(existingGlobalVariables.map(variable => variable.name));
}
/** Return the variables information for a particular scope of the underlying stack frame */
public async variablesOfScope(scopeName: VariablesScopeName): Promise<DebugProtocol.Variable[]> {
return singleElementOfArray(await this.variablesOfScopes([scopeName])).variables;
}
}

Просмотреть файл

@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IVariableInformation {
name: string;
value: string;
type?: string;
}
/**
* Print a collection of variable informations to make it easier to compare
* the expected variables of a test, and the actual variables of the debuggee
*/
export function printVariables(variables: IVariableInformation[]): string {
const variablesPrinted = variables.map(variable => printVariable(variable));
return variablesPrinted.join('\n');
}
function printVariable(variable: IVariableInformation): string {
return `${variable.name} = ${variable.value} (${(variable.type)})`;
}

Просмотреть файл

@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as _ from 'lodash';
import { expect } from 'chai';
import { DebugProtocol } from 'vscode-debugprotocol';
import { trimWhitespaceAndComments } from '../breakpoints/implementation/printedTestInputl';
import { ManyVariablesValues, } from './variablesWizard';
import { printVariables } from './variablesPrinting';
/** Whether the expected variables should match exactly the actual variables of the debuggee
* or whether the expected variables should only be a subset of the actual variables of the debuggee
*/
export enum KindOfVerification {
SameAndExact, /** Same exact variables */
ProperSubset /** Expected variables are a subset of the actual variables */
}
/**
* Provide methods to validate that the variables appearing on the stack trace are what we expect
*/
export class VariablesVerifier {
/** Verify that the actual variables are exactly the variables that we expect */
public assertVariablesAre(variables: DebugProtocol.Variable[], expectedVariables: string | ManyVariablesValues): void {
if (typeof expectedVariables === 'string') {
this.assertVariablesPrintedAre(variables, expectedVariables);
} else {
this.assertVariablesValuesAre(variables, expectedVariables);
}
}
private assertVariablesPrintedAre(variables: DebugProtocol.Variable[], expectedVariablesPrinted: string): void {
const trimmedVariables = trimWhitespaceAndComments(expectedVariablesPrinted);
expect(printVariables(variables)).to.equal(trimmedVariables);
}
private assertVariablesValuesAre(manyVariables: DebugProtocol.Variable[], expectedVariablesValues: ManyVariablesValues): void {
return this.assertVariablesValuesSatisfy(manyVariables, expectedVariablesValues, KindOfVerification.SameAndExact);
}
/** Verify that the actual variables include as a proper subset the variables that we expect */
public assertVariablesValuesContain(manyVariables: DebugProtocol.Variable[], expectedVariablesValues: ManyVariablesValues): void {
return this.assertVariablesValuesSatisfy(manyVariables, expectedVariablesValues, KindOfVerification.ProperSubset);
}
/** Verify that the actual variables match the expected variables with the verification specified as a parameter (Same or subset) */
public assertVariablesValuesSatisfy(
manyVariables: DebugProtocol.Variable[], expectedVariablesValues: ManyVariablesValues,
kindOfVerification: KindOfVerification): void {
const actualVariableNames = manyVariables.map(variable => variable.name);
const expectedVariablesNames = Object.keys(expectedVariablesValues);
switch (kindOfVerification) {
case KindOfVerification.ProperSubset:
expect(actualVariableNames).to.contain.members(expectedVariablesNames);
break;
case KindOfVerification.SameAndExact:
expect(actualVariableNames).to.have.members(expectedVariablesNames);
break;
default:
throw new Error(`Unexpected comparison algorithm: ${kindOfVerification}`);
}
for (const variable of manyVariables) {
const variableName = variable.name;
if (expectedVariablesNames.indexOf(variableName) >= 0) {
const expectedValue = expectedVariablesValues[variableName];
expect(expectedValue).to.not.equal(undefined);
expect(variable!.evaluateName).to.be.equal(variable!.name); // Is this ever different?
expect(variable!.variablesReference).to.be.greaterThan(-1);
expect(variable!.value).to.be.equal(`${expectedValue}`);
// TODO: Validate variable type too
} else {
expect(kindOfVerification).to.equal(KindOfVerification.ProperSubset); // This should not happen for same elements
}
}
}
}

Просмотреть файл

@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as _ from 'lodash';
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
import { PromiseOrNot } from 'vscode-chrome-debug-core';
import { StackFrameWizard } from './stackFrameWizard';
import { VariablesVerifier } from './variablesVerifier';
import { ValidatedMap } from '../../core-v2/chrome/collections/validatedMap';
import { trimWhitespaceAndComments } from '../breakpoints/implementation/printedTestInputl';
import { expect } from 'chai';
import { printVariables } from './variablesPrinting';
export interface VariablePrintedProperties {
value: string;
type: string;
}
export interface ManyVariablePrintedProperties {
[variableName: string]: VariablePrintedProperties;
}
export interface ManyVariablesValues {
[variableName: string]: unknown;
}
export type ManyVariablesPropertiesPrinted = string; // `${variable.name} = ${variable.value} ${(variable.type)}\n`
export type IScopeExpectedVariables = ManyVariablesPropertiesPrinted | ManyVariablesValues;
export interface IExpectedVariables {
script?: IScopeExpectedVariables;
local?: IScopeExpectedVariables;
global?: IScopeExpectedVariables;
catch?: IScopeExpectedVariables;
block?: IScopeExpectedVariables;
closure?: IScopeExpectedVariables;
eval?: IScopeExpectedVariables;
with?: IScopeExpectedVariables;
module?: IScopeExpectedVariables;
local_contains?: ManyVariablesValues;
}
export type VariablesScopeName = keyof IExpectedVariables;
export type VerificationModifier = 'contains' | '';
export class VariablesWizard {
public constructor(private readonly _client: ExtendedDebugClient) { }
/** Verify that the global variables have the expected values, ignoring the variables in <namesOfGlobalsToIgnore> */
public async assertNewGlobalVariariablesAre(actionThatAddsNewVariables: () => PromiseOrNot<void>, expectedGlobals: ManyVariablesPropertiesPrinted): Promise<void> {
// Store pre-existing global variables' names
const namesOfGlobalsToIgnore = await (await this.topStackFrameHelper()).globalVariableNames();
// Perform an action that adds new global variables
await actionThatAddsNewVariables();
const globalsOnFrame = await (await this.topStackFrameHelper()).variablesOfScope('global');
const nonIgnoredGlobals = globalsOnFrame.filter(global => !namesOfGlobalsToIgnore.has(global.name));
const expectedGlobalsTrimmed = trimWhitespaceAndComments(expectedGlobals);
expect(printVariables(nonIgnoredGlobals)).to.equal(expectedGlobalsTrimmed);
}
/**
* Verify that the stackFrame contains some variables with a specific value
*/
public async assertTopFrameVariablesAre(verifications: IExpectedVariables): Promise<void> {
await this.assertStackFrameVariablesAre(await this.topStackFrameHelper(), verifications);
}
public async assertStackFrameVariablesAre(stackFrame: StackFrameWizard, verifications: IExpectedVariables) {
const scopesWithModifiers = Object.keys(verifications);
const scopesWithoutModifiers = scopesWithModifiers.map(s => this.splitIntoScopeNameAndModifier(s)[0]);
const withoutModifiersToWith = new ValidatedMap(_.zip(scopesWithoutModifiers, scopesWithModifiers));
const manyScopes = await (stackFrame).variablesOfScopes(scopesWithoutModifiers);
for (const scope of manyScopes) {
const scopeNameWithModifier = withoutModifiersToWith.get(scope.scopeName)!;
const [, modifier] = this.splitIntoScopeNameAndModifier(scopeNameWithModifier);
switch (modifier) {
case '':
this.verificator.assertVariablesAre(scope.variables, verifications[scopeNameWithModifier]!);
break;
case 'contains':
this.verificator.assertVariablesValuesContain(scope.variables, <ManyVariablesValues>verifications[scopeNameWithModifier]!);
break;
default:
throw new Error(`Unknown modified used for variables verification: ${modifier} in ${scopeNameWithModifier}`);
}
}
}
private splitIntoScopeNameAndModifier(modifiedScopeName: keyof IExpectedVariables): [VariablesScopeName, VerificationModifier] {
const components = modifiedScopeName.split('_');
if (components.length > 2) {
throw new Error(`Invalid modified scope name: ${modifiedScopeName}`);
}
return [<VariablesScopeName>components[0], <VerificationModifier>_.defaultTo(components[1], '')];
}
private get verificator(): VariablesVerifier {
return new VariablesVerifier();
}
private async topStackFrameHelper(): Promise<StackFrameWizard> {
return await StackFrameWizard.topStackFrame(this._client);
}
}

Просмотреть файл

@ -4,6 +4,8 @@
import * as path from 'path';
import * as mockery from 'mockery';
import { execSync } from 'child_process';
import * as puppeteer from 'puppeteer';
export function setupUnhandledRejectionListener(): void {
process.addListener('unhandledRejection', unhandledRejectionListener);
@ -13,7 +15,7 @@ export function removeUnhandledRejectionListener(): void {
process.removeListener('unhandledRejection', unhandledRejectionListener);
}
function unhandledRejectionListener(reason: any, p: Promise<any>) {
function unhandledRejectionListener(reason: any, _p: Promise<any>) {
console.log('*');
console.log('**');
console.log('***');
@ -49,6 +51,23 @@ export function registerLocMocks(): void {
});
}
function dummyLocalize(id: string, englishString: string): string {
/**
* Kills all running instances of chrome (that were launched by the tests, on Windows at least) on the test host
*/
export function killAllChrome() {
try {
const killCmd = (process.platform === 'win32') ? `start powershell -WindowStyle hidden -Command "Get-Process | Where-Object {$_.Path -like '*${puppeteer.executablePath()}*'} | Stop-Process"` : 'killall chrome';
const hideWindows = process.env['TEST_DA_HIDE_WINDOWS'] === 'true';
const output = execSync(killCmd, { windowsHide: hideWindows }); // TODO: windowsHide paramenter doesn't currently work. It might be related to this: https://github.com/nodejs/node/issues/21825
if (output.length > 0) { // Don't print empty lines
console.log(output.toString());
}
} catch (e) {
console.error(`Error killing chrome: ${e.message}`);
}
// the kill command will exit with a non-zero code (and cause execSync to throw) if chrome is already stopped
}
function dummyLocalize(_id: string, englishString: string): string {
return englishString;
}

20
testdata/inline_scripts/.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,20 @@
{
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome",
"trace": "verbose",
//"url": "http://localhost:5000/other.html",
"file": "C:/development/debug_adapters_v2/vscode-chrome-debug/testdata/inline_scripts/other.html",
"webRoot": "${workspaceFolder}"
},
{
"type": "my-chrome",
"request": "launch",
"name": "My Launch Chrome",
"url": "http://localhost:5000/other.html",
"webRoot": "${workspaceFolder}"
}
]
}

64
testdata/inline_scripts/multiple.html поставляемый Normal file
Просмотреть файл

@ -0,0 +1,64 @@
<!doctype html>
<html>
<head>
</head>
<body>
<h1>Hello, world!</h1>
<script>
console.log('first script of the day!');
function blub2() {
console.log('EAC before');
let a = 0;
console.log('EAC after')
let b = 2;
let c = a + b; // bpLabel: inlineScript1
}
</script>
<input type="button" value="blub" id="actionButton" onclick="blub2()">
<script>
function blub3() {
console.log('EAC before');
let a = 0;
console.log('EAC after')
let b = 2;
let c = a + b; // bpLabel: inlineScript2
}
</script>
<script>
function blub4() {
console.log('EAC before');
let a = 0;
console.log('EAC after')
let b = 2;
let c = a + b; // bpLabel: inlineScript3
}
</script>
<input type="button" value="blub" id="actionButton" onclick="blub4()">
</body>
</html>

30
testdata/inline_scripts/single.html поставляемый Normal file
Просмотреть файл

@ -0,0 +1,30 @@
<!doctype html>
<html>
<head>
</head>
<body>
<h1>Hello, world!</h1>
<script>
console.log('first script of the day!');
function blub2() {
console.log('EAC before');
let a = 0;
console.log('EAC after')
let b = 2;
let c = a + b; // bpLabel: inlineScriptSingle1
}
</script>
<input type="button" value="blub" id="actionButton" onclick="blub2()">
</body>
</html>

23
testdata/react_with_loop/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

15
testdata/react_with_loop/.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:5000",
"webRoot": "${workspaceFolder}/dist"
}
]
}

4
testdata/react_with_loop/README.md поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
Test project for the React framework, created using [Create React App](https://github.com/facebook/create-react-app).
Can re-generate the project output with
### `npm run build`

93
testdata/react_with_loop/config/env.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,93 @@
'use strict';
const fs = require('fs');
const path = require('path');
const paths = require('./paths');
// Make sure that including paths.js after env.js will read .env variables.
delete require.cache[require.resolve('./paths')];
const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
throw new Error(
'The NODE_ENV environment variable is required but was not specified.'
);
}
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
var dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`,
`${paths.dotenv}.${NODE_ENV}`,
// Don't include `.env.local` for `test` environment
// since normally you expect tests to produce the same
// results for everyone
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
paths.dotenv,
].filter(Boolean);
// Load environment variables from .env* files. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set. Variable expansion is supported in .env files.
// https://github.com/motdotla/dotenv
// https://github.com/motdotla/dotenv-expand
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv-expand')(
require('dotenv').config({
path: dotenvFile,
})
);
}
});
// We support resolving modules according to `NODE_PATH`.
// This lets you use absolute paths in imports inside large monorepos:
// https://github.com/facebook/create-react-app/issues/253.
// It works similar to `NODE_PATH` in Node itself:
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
// We also resolve them to make sure all tools using them work consistently.
const appDirectory = fs.realpathSync(process.cwd());
process.env.NODE_PATH = (process.env.NODE_PATH || '')
.split(path.delimiter)
.filter(folder => folder && !path.isAbsolute(folder))
.map(folder => path.resolve(appDirectory, folder))
.join(path.delimiter);
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
// injected into the application via DefinePlugin in Webpack configuration.
const REACT_APP = /^REACT_APP_/i;
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key];
return env;
},
{
// Useful for determining whether were running in production mode.
// Most importantly, it switches React into the correct mode.
NODE_ENV: process.env.NODE_ENV || 'development',
// Useful for resolving the correct path to static assets in `public`.
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
// This should only be used as an escape hatch. Normally you would put
// images into the `src` and `import` them in code to get their paths.
PUBLIC_URL: publicUrl,
}
);
// Stringify all values so we can feed into Webpack DefinePlugin
const stringified = {
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
};
return { raw, stringified };
}
module.exports = getClientEnvironment;

14
testdata/react_with_loop/config/jest/cssTransform.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,14 @@
'use strict';
// This is a custom Jest transformer turning style imports into empty objects.
// http://facebook.github.io/jest/docs/en/webpack.html
module.exports = {
process() {
return 'module.exports = {};';
},
getCacheKey() {
// The output is always the same.
return 'cssTransform';
},
};

31
testdata/react_with_loop/config/jest/fileTransform.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,31 @@
'use strict';
const path = require('path');
// This is a custom Jest transformer turning file imports into filenames.
// http://facebook.github.io/jest/docs/en/webpack.html
module.exports = {
process(src, filename) {
const assetFilename = JSON.stringify(path.basename(filename));
if (filename.match(/\.svg$/)) {
return `const React = require('react');
module.exports = {
__esModule: true,
default: ${assetFilename},
ReactComponent: React.forwardRef((props, ref) => ({
$$typeof: Symbol.for('react.element'),
type: 'svg',
ref: ref,
key: null,
props: Object.assign({}, props, {
children: ${assetFilename}
})
})),
};`;
}
return `module.exports = ${assetFilename};`;
},
};

89
testdata/react_with_loop/config/paths.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,89 @@
'use strict';
const path = require('path');
const fs = require('fs');
const url = require('url');
// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
const envPublicUrl = process.env.PUBLIC_URL;
function ensureSlash(inputPath, needsSlash) {
const hasSlash = inputPath.endsWith('/');
if (hasSlash && !needsSlash) {
return inputPath.substr(0, inputPath.length - 1);
} else if (!hasSlash && needsSlash) {
return `${inputPath}/`;
} else {
return inputPath;
}
}
const getPublicUrl = appPackageJson =>
envPublicUrl || require(appPackageJson).homepage;
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
// "public path" at which the app is served.
// Webpack needs to know it to put the right <script> hrefs into HTML even in
// single-page apps that may serve index.html for nested URLs like /todos/42.
// We can't use a relative path in HTML because we don't want to load something
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
function getServedPath(appPackageJson) {
const publicUrl = getPublicUrl(appPackageJson);
const servedUrl =
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
return ensureSlash(servedUrl, true);
}
const moduleFileExtensions = [
'web.mjs',
'mjs',
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
];
// Resolve file paths in the same order as webpack
const resolveModule = (resolveFn, filePath) => {
const extension = moduleFileExtensions.find(extension =>
fs.existsSync(resolveFn(`${filePath}.${extension}`))
);
if (extension) {
return resolveFn(`${filePath}.${extension}`);
}
return resolveFn(`${filePath}.js`);
};
// config after eject: we're in ./config/
module.exports = {
dotenv: resolveApp('.env'),
appPath: resolveApp('.'),
appBuild: resolveApp('build'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
appTsConfig: resolveApp('tsconfig.json'),
yarnLockFile: resolveApp('yarn.lock'),
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
publicUrl: getPublicUrl(resolveApp('package.json')),
servedPath: getServedPath(resolveApp('package.json')),
};
module.exports.moduleFileExtensions = moduleFileExtensions;

608
testdata/react_with_loop/config/webpack.config.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,608 @@
'use strict';
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
const CopyPlugin = require('copy-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
console.log("using sourcemaps: " + shouldUseSourceMap);
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function(webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// In development, we always serve from the root. This makes config easier.
const publicPath = isEnvProduction
? paths.servedPath
: isEnvDevelopment && '/';
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './';
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = isEnvProduction
? publicPath.slice(0, -1)
: isEnvDevelopment && '';
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
options: Object.assign(
{},
shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
),
},
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
].filter(Boolean);
if (preProcessor) {
loaders.push({
loader: require.resolve(preProcessor),
options: {
sourceMap: isEnvProduction && shouldUseSourceMap,
},
});
}
return loaders;
};
return {
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
devtool: false,
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
entry: [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
//isEnvDevelopment &&
// require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),
output: {
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'out/[name].[contenthash:8].js'
: isEnvDevelopment && 'out/bundle.js',
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'out/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'out/[name].chunk.js',
// We inferred the "public path" (such as / or /my-project) from homepage.
// We use "/" in development.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
},
optimization: {
minimize: isEnvProduction,
minimizer: [
// This is only used in production mode
new TerserPlugin({
terserOptions: {
parse: {
// we want terser to parse ecma 8 code. However, we don't want it
// to apply any minfication steps that turns valid ecma 5 code
// into invalid ecma 5 code. This is why the 'compress' and 'output'
// sections only apply transformations that are ecma 5 safe
// https://github.com/facebook/create-react-app/pull/4234
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebook/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
// Disabled because of an issue with Terser breaking valid code:
// https://github.com/facebook/create-react-app/issues/5250
// Pending futher investigation:
// https://github.com/terser-js/terser/issues/120
inline: 2,
},
mangle: {
safari10: true,
},
output: {
ecma: 5,
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488
ascii_only: true,
},
},
// Use multi-process parallel running to improve the build speed
// Default number of concurrent runs: os.cpus().length - 1
parallel: true,
// Enable file caching
cache: true,
sourceMap: shouldUseSourceMap,
}),
// This is only used in production mode
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
parser: safePostCssParser,
map: shouldUseSourceMap
? {
// `inline: false` forces the sourcemap to be output into a
// separate file
inline: false,
// `annotation: true` appends the sourceMappingURL to the end of
// the css file, helping the browser find the sourcemap
annotation: true,
}
: false,
},
}),
],
// Automatically split vendor and commons
// https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: {
chunks: 'all',
name: false,
},
// Keep the runtime chunk separated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985
runtimeChunk: true,
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebook/create-react-app/issues/253
modules: ['node_modules'].concat(
// It is guaranteed to exist because we tweak it in `env.js`
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebook/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: paths.moduleFileExtensions
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
},
plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
// guards against forgotten dependencies and such.
PnpWebpackPlugin,
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
resolveLoader: {
plugins: [
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
// from the current package.
PnpWebpackPlugin.moduleLoader(module),
],
},
module: {
strictExportPresence: true,
rules: [
// Disable require.ensure as it's not a standard language feature.
{ parser: { requireEnsure: false } },
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.(js|mjs|jsx)$/,
enforce: 'pre',
use: [
{
options: {
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'out/media/[name].[hash:8].[ext]',
},
},
// Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent: '@svgr/webpack?-svgo,+ref![path]',
},
},
},
],
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
cacheCompression: isEnvProduction,
compact: isEnvProduction,
},
},
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true },
],
],
cacheDirectory: true,
cacheCompression: isEnvProduction,
// If an error happens in a package, it's possible to be
// because it was compiled. Thus, we don't want the browser
// debugger to show the original code. Instead, the code
// being evaluated would be much more helpful.
sourceMaps: false,
},
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use MiniCSSExtractPlugin to extract that CSS
// to a file, but in development "style" loader enables hot editing
// of CSS.
// By default we support CSS Modules with the extension .module.css
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
// using the extension .module.css
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
}),
},
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
},
'sass-loader'
),
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
name: 'out/media/[name].[hash:8].[ext]',
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
},
plugins: [
new webpack.SourceMapDevToolPlugin({
filename: '[file].map'
}),
// clean the dist directory before a build
new CleanWebpackPlugin(),
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
{},
{
filename: 'index.html',
inject: true,
template: paths.appHtml,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
// Inlines the webpack runtime script. This script is too small to warrant
// a network request.
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In production, it will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL.
// In development, this will be an empty string.
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// This gives some necessary context to module not found errors, such as
// the requesting resource.
new ModuleNotFoundPlugin(paths.appPath),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV is set to production
// during a production build.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have
// to restart the development server for Webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebook/create-react-app/issues/186
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
isEnvProduction &&
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'out/css/[name].[contenthash:8].css',
chunkFilename: 'out/css/[name].[contenthash:8].chunk.css',
}),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build.
isEnvProduction &&
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
exclude: [/\.map$/, /asset-manifest\.json$/],
importWorkboxFrom: 'cdn',
navigateFallback: publicUrl + '/index.html',
navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'),
// Exclude URLs containing a dot, as they're likely a resource in
// public/ and not a SPA route
new RegExp('/[^/]+\\.[^/]+$'),
],
}),
// TypeScript type checking
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
typescript: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
async: isEnvDevelopment,
useTypescriptIncrementalApi: true,
checkSyntacticErrors: true,
tsconfig: paths.appTsConfig,
reportFiles: [
'**',
'!**/*.json',
'!**/__tests__/**',
'!**/?(*.)(spec|test).*',
'!**/src/setupProxy.*',
'!**/src/setupTests.*',
],
watch: paths.appSrc,
silent: true,
// The formatter is invoked directly in WebpackDevServerUtils during development
formatter: isEnvProduction ? typescriptFormatter : undefined,
}),
new CopyPlugin([
{ from: 'src', to: 'src' }
])
].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
module: 'empty',
dgram: 'empty',
dns: 'mock',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
// Turn off performance processing because we utilize
// our own hints via the FileSizeReporter
performance: false,
};
};

104
testdata/react_with_loop/config/webpackDevServer.config.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,104 @@
'use strict';
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
const ignoredFiles = require('react-dev-utils/ignoredFiles');
const paths = require('./paths');
const fs = require('fs');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const host = process.env.HOST || '0.0.0.0';
module.exports = function(proxy, allowedHost) {
return {
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
// websites from potentially accessing local content through DNS rebinding:
// https://github.com/webpack/webpack-dev-server/issues/887
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
// However, it made several existing use cases such as development in cloud
// environment or subdomains in development significantly more complicated:
// https://github.com/facebook/create-react-app/issues/2271
// https://github.com/facebook/create-react-app/issues/2233
// While we're investigating better solutions, for now we will take a
// compromise. Since our WDS configuration only serves files in the `public`
// folder we won't consider accessing them a vulnerability. However, if you
// use the `proxy` feature, it gets more dangerous because it can expose
// remote code execution vulnerabilities in backends like Django and Rails.
// So we will disable the host check normally, but enable it if you have
// specified the `proxy` setting. Finally, we let you override it if you
// really know what you're doing with a special environment variable.
disableHostCheck:
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
// Enable gzip compression of generated files.
compress: true,
// Silence WebpackDevServer's own logs since they're generally not useful.
// It will still show compile warnings and errors with this setting.
clientLogLevel: 'none',
// By default WebpackDevServer serves physical files from current directory
// in addition to all the virtual build products that it serves from memory.
// This is confusing because those files wont automatically be available in
// production build folder unless we copy them. However, copying the whole
// project directory is dangerous because we may expose sensitive files.
// Instead, we establish a convention that only files in `public` directory
// get served. Our build script will copy `public` into the `build` folder.
// In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
// Note that we only recommend to use `public` folder as an escape hatch
// for files like `favicon.ico`, `manifest.json`, and libraries that are
// for some reason broken when imported through Webpack. If you just want to
// use an image, put it in `src` and `import` it from JavaScript instead.
contentBase: paths.appPublic,
// By default files from `contentBase` will not trigger a page reload.
watchContentBase: true,
// Enable hot reloading server. It will provide /sockjs-node/ endpoint
// for the WebpackDevServer client so it can learn when the files were
// updated. The WebpackDevServer client is included as an entry point
// in the Webpack development configuration. Note that only changes
// to CSS are currently hot reloaded. JS changes will refresh the browser.
hot: true,
// It is important to tell WebpackDevServer to use the same "root" path
// as we specified in the config. In development, we always serve from /.
publicPath: '/',
// WebpackDevServer is noisy by default so we emit custom message instead
// by listening to the compiler events with `compiler.hooks[...].tap` calls above.
quiet: true,
// Reportedly, this avoids CPU overload on some systems.
// https://github.com/facebook/create-react-app/issues/293
// src/node_modules is not ignored to support absolute imports
// https://github.com/facebook/create-react-app/issues/1065
watchOptions: {
ignored: ignoredFiles(paths.appSrc),
},
// Enable HTTPS if the HTTPS environment variable is set to 'true'
https: protocol === 'https',
host,
overlay: false,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true,
},
public: allowedHost,
proxy,
before(app, server) {
if (fs.existsSync(paths.proxySetup)) {
// This registers user provided middleware for proxy reasons
require(paths.proxySetup)(app);
}
// This lets us fetch source contents from webpack for the error overlay
app.use(evalSourceMapMiddleware(server));
// This lets us open files from the runtime error overlay.
app.use(errorOverlayMiddleware());
// This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination.
// We do this in development to avoid hitting the production cache if
// it used the same host and port.
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
app.use(noopServiceWorkerMiddleware());
},
};
};

37
testdata/react_with_loop/dist/index.html поставляемый Normal file
Просмотреть файл

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
Notice the use of in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script src="/out/bundle.js"></script><script src="/out/0.chunk.js"></script><script src="/out/main.chunk.js"></script></body>
</html>

7722
testdata/react_with_loop/dist/out/0.chunk.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

1
testdata/react_with_loop/dist/out/0.chunk.js.map поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

860
testdata/react_with_loop/dist/out/bundle.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,860 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ function webpackJsonpCallback(data) {
/******/ var chunkIds = data[0];
/******/ var moreModules = data[1];
/******/ var executeModules = data[2];
/******/
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ // add entry modules from loaded chunk to deferred list
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/ // run deferred modules when all chunks ready
/******/ return checkDeferredModules();
/******/ };
/******/ function checkDeferredModules() {
/******/ var result;
/******/ for(var i = 0; i < deferredModules.length; i++) {
/******/ var deferredModule = deferredModules[i];
/******/ var fulfilled = true;
/******/ for(var j = 1; j < deferredModule.length; j++) {
/******/ var depId = deferredModule[j];
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
/******/ }
/******/ if(fulfilled) {
/******/ deferredModules.splice(i--, 1);
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ }
/******/ }
/******/ return result;
/******/ }
/******/ function hotDisposeChunk(chunkId) {
/******/ delete installedChunks[chunkId];
/******/ }
/******/ var parentHotUpdateCallback = window["webpackHotUpdate"];
/******/ window["webpackHotUpdate"] = // eslint-disable-next-line no-unused-vars
/******/ function webpackHotUpdateCallback(chunkId, moreModules) {
/******/ hotAddUpdateChunk(chunkId, moreModules);
/******/ if (parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules);
/******/ } ;
/******/
/******/ // eslint-disable-next-line no-unused-vars
/******/ function hotDownloadUpdateChunk(chunkId) {
/******/ var script = document.createElement("script");
/******/ script.charset = "utf-8";
/******/ script.src = __webpack_require__.p + "" + chunkId + "." + hotCurrentHash + ".hot-update.js";
/******/ if (null) script.crossOrigin = null;
/******/ document.head.appendChild(script);
/******/ }
/******/
/******/ // eslint-disable-next-line no-unused-vars
/******/ function hotDownloadManifest(requestTimeout) {
/******/ requestTimeout = requestTimeout || 10000;
/******/ return new Promise(function(resolve, reject) {
/******/ if (typeof XMLHttpRequest === "undefined") {
/******/ return reject(new Error("No browser support"));
/******/ }
/******/ try {
/******/ var request = new XMLHttpRequest();
/******/ var requestPath = __webpack_require__.p + "" + hotCurrentHash + ".hot-update.json";
/******/ request.open("GET", requestPath, true);
/******/ request.timeout = requestTimeout;
/******/ request.send(null);
/******/ } catch (err) {
/******/ return reject(err);
/******/ }
/******/ request.onreadystatechange = function() {
/******/ if (request.readyState !== 4) return;
/******/ if (request.status === 0) {
/******/ // timeout
/******/ reject(
/******/ new Error("Manifest request to " + requestPath + " timed out.")
/******/ );
/******/ } else if (request.status === 404) {
/******/ // no update available
/******/ resolve();
/******/ } else if (request.status !== 200 && request.status !== 304) {
/******/ // other failure
/******/ reject(new Error("Manifest request to " + requestPath + " failed."));
/******/ } else {
/******/ // success
/******/ try {
/******/ var update = JSON.parse(request.responseText);
/******/ } catch (e) {
/******/ reject(e);
/******/ return;
/******/ }
/******/ resolve(update);
/******/ }
/******/ };
/******/ });
/******/ }
/******/
/******/ var hotApplyOnUpdate = true;
/******/ // eslint-disable-next-line no-unused-vars
/******/ var hotCurrentHash = "509672cda847b6f82bc3";
/******/ var hotRequestTimeout = 10000;
/******/ var hotCurrentModuleData = {};
/******/ var hotCurrentChildModule;
/******/ // eslint-disable-next-line no-unused-vars
/******/ var hotCurrentParents = [];
/******/ // eslint-disable-next-line no-unused-vars
/******/ var hotCurrentParentsTemp = [];
/******/
/******/ // eslint-disable-next-line no-unused-vars
/******/ function hotCreateRequire(moduleId) {
/******/ var me = installedModules[moduleId];
/******/ if (!me) return __webpack_require__;
/******/ var fn = function(request) {
/******/ if (me.hot.active) {
/******/ if (installedModules[request]) {
/******/ if (installedModules[request].parents.indexOf(moduleId) === -1) {
/******/ installedModules[request].parents.push(moduleId);
/******/ }
/******/ } else {
/******/ hotCurrentParents = [moduleId];
/******/ hotCurrentChildModule = request;
/******/ }
/******/ if (me.children.indexOf(request) === -1) {
/******/ me.children.push(request);
/******/ }
/******/ } else {
/******/ console.warn(
/******/ "[HMR] unexpected require(" +
/******/ request +
/******/ ") from disposed module " +
/******/ moduleId
/******/ );
/******/ hotCurrentParents = [];
/******/ }
/******/ return __webpack_require__(request);
/******/ };
/******/ var ObjectFactory = function ObjectFactory(name) {
/******/ return {
/******/ configurable: true,
/******/ enumerable: true,
/******/ get: function() {
/******/ return __webpack_require__[name];
/******/ },
/******/ set: function(value) {
/******/ __webpack_require__[name] = value;
/******/ }
/******/ };
/******/ };
/******/ for (var name in __webpack_require__) {
/******/ if (
/******/ Object.prototype.hasOwnProperty.call(__webpack_require__, name) &&
/******/ name !== "e" &&
/******/ name !== "t"
/******/ ) {
/******/ Object.defineProperty(fn, name, ObjectFactory(name));
/******/ }
/******/ }
/******/ fn.e = function(chunkId) {
/******/ if (hotStatus === "ready") hotSetStatus("prepare");
/******/ hotChunksLoading++;
/******/ return __webpack_require__.e(chunkId).then(finishChunkLoading, function(err) {
/******/ finishChunkLoading();
/******/ throw err;
/******/ });
/******/
/******/ function finishChunkLoading() {
/******/ hotChunksLoading--;
/******/ if (hotStatus === "prepare") {
/******/ if (!hotWaitingFilesMap[chunkId]) {
/******/ hotEnsureUpdateChunk(chunkId);
/******/ }
/******/ if (hotChunksLoading === 0 && hotWaitingFiles === 0) {
/******/ hotUpdateDownloaded();
/******/ }
/******/ }
/******/ }
/******/ };
/******/ fn.t = function(value, mode) {
/******/ if (mode & 1) value = fn(value);
/******/ return __webpack_require__.t(value, mode & ~1);
/******/ };
/******/ return fn;
/******/ }
/******/
/******/ // eslint-disable-next-line no-unused-vars
/******/ function hotCreateModule(moduleId) {
/******/ var hot = {
/******/ // private stuff
/******/ _acceptedDependencies: {},
/******/ _declinedDependencies: {},
/******/ _selfAccepted: false,
/******/ _selfDeclined: false,
/******/ _disposeHandlers: [],
/******/ _main: hotCurrentChildModule !== moduleId,
/******/
/******/ // Module API
/******/ active: true,
/******/ accept: function(dep, callback) {
/******/ if (dep === undefined) hot._selfAccepted = true;
/******/ else if (typeof dep === "function") hot._selfAccepted = dep;
/******/ else if (typeof dep === "object")
/******/ for (var i = 0; i < dep.length; i++)
/******/ hot._acceptedDependencies[dep[i]] = callback || function() {};
/******/ else hot._acceptedDependencies[dep] = callback || function() {};
/******/ },
/******/ decline: function(dep) {
/******/ if (dep === undefined) hot._selfDeclined = true;
/******/ else if (typeof dep === "object")
/******/ for (var i = 0; i < dep.length; i++)
/******/ hot._declinedDependencies[dep[i]] = true;
/******/ else hot._declinedDependencies[dep] = true;
/******/ },
/******/ dispose: function(callback) {
/******/ hot._disposeHandlers.push(callback);
/******/ },
/******/ addDisposeHandler: function(callback) {
/******/ hot._disposeHandlers.push(callback);
/******/ },
/******/ removeDisposeHandler: function(callback) {
/******/ var idx = hot._disposeHandlers.indexOf(callback);
/******/ if (idx >= 0) hot._disposeHandlers.splice(idx, 1);
/******/ },
/******/
/******/ // Management API
/******/ check: hotCheck,
/******/ apply: hotApply,
/******/ status: function(l) {
/******/ if (!l) return hotStatus;
/******/ hotStatusHandlers.push(l);
/******/ },
/******/ addStatusHandler: function(l) {
/******/ hotStatusHandlers.push(l);
/******/ },
/******/ removeStatusHandler: function(l) {
/******/ var idx = hotStatusHandlers.indexOf(l);
/******/ if (idx >= 0) hotStatusHandlers.splice(idx, 1);
/******/ },
/******/
/******/ //inherit from previous dispose call
/******/ data: hotCurrentModuleData[moduleId]
/******/ };
/******/ hotCurrentChildModule = undefined;
/******/ return hot;
/******/ }
/******/
/******/ var hotStatusHandlers = [];
/******/ var hotStatus = "idle";
/******/
/******/ function hotSetStatus(newStatus) {
/******/ hotStatus = newStatus;
/******/ for (var i = 0; i < hotStatusHandlers.length; i++)
/******/ hotStatusHandlers[i].call(null, newStatus);
/******/ }
/******/
/******/ // while downloading
/******/ var hotWaitingFiles = 0;
/******/ var hotChunksLoading = 0;
/******/ var hotWaitingFilesMap = {};
/******/ var hotRequestedFilesMap = {};
/******/ var hotAvailableFilesMap = {};
/******/ var hotDeferred;
/******/
/******/ // The update info
/******/ var hotUpdate, hotUpdateNewHash;
/******/
/******/ function toModuleId(id) {
/******/ var isNumber = +id + "" === id;
/******/ return isNumber ? +id : id;
/******/ }
/******/
/******/ function hotCheck(apply) {
/******/ if (hotStatus !== "idle") {
/******/ throw new Error("check() is only allowed in idle status");
/******/ }
/******/ hotApplyOnUpdate = apply;
/******/ hotSetStatus("check");
/******/ return hotDownloadManifest(hotRequestTimeout).then(function(update) {
/******/ if (!update) {
/******/ hotSetStatus("idle");
/******/ return null;
/******/ }
/******/ hotRequestedFilesMap = {};
/******/ hotWaitingFilesMap = {};
/******/ hotAvailableFilesMap = update.c;
/******/ hotUpdateNewHash = update.h;
/******/
/******/ hotSetStatus("prepare");
/******/ var promise = new Promise(function(resolve, reject) {
/******/ hotDeferred = {
/******/ resolve: resolve,
/******/ reject: reject
/******/ };
/******/ });
/******/ hotUpdate = {};
/******/ for(var chunkId in installedChunks)
/******/ // eslint-disable-next-line no-lone-blocks
/******/ {
/******/ /*globals chunkId */
/******/ hotEnsureUpdateChunk(chunkId);
/******/ }
/******/ if (
/******/ hotStatus === "prepare" &&
/******/ hotChunksLoading === 0 &&
/******/ hotWaitingFiles === 0
/******/ ) {
/******/ hotUpdateDownloaded();
/******/ }
/******/ return promise;
/******/ });
/******/ }
/******/
/******/ // eslint-disable-next-line no-unused-vars
/******/ function hotAddUpdateChunk(chunkId, moreModules) {
/******/ if (!hotAvailableFilesMap[chunkId] || !hotRequestedFilesMap[chunkId])
/******/ return;
/******/ hotRequestedFilesMap[chunkId] = false;
/******/ for (var moduleId in moreModules) {
/******/ if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ hotUpdate[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if (--hotWaitingFiles === 0 && hotChunksLoading === 0) {
/******/ hotUpdateDownloaded();
/******/ }
/******/ }
/******/
/******/ function hotEnsureUpdateChunk(chunkId) {
/******/ if (!hotAvailableFilesMap[chunkId]) {
/******/ hotWaitingFilesMap[chunkId] = true;
/******/ } else {
/******/ hotRequestedFilesMap[chunkId] = true;
/******/ hotWaitingFiles++;
/******/ hotDownloadUpdateChunk(chunkId);
/******/ }
/******/ }
/******/
/******/ function hotUpdateDownloaded() {
/******/ hotSetStatus("ready");
/******/ var deferred = hotDeferred;
/******/ hotDeferred = null;
/******/ if (!deferred) return;
/******/ if (hotApplyOnUpdate) {
/******/ // Wrap deferred object in Promise to mark it as a well-handled Promise to
/******/ // avoid triggering uncaught exception warning in Chrome.
/******/ // See https://bugs.chromium.org/p/chromium/issues/detail?id=465666
/******/ Promise.resolve()
/******/ .then(function() {
/******/ return hotApply(hotApplyOnUpdate);
/******/ })
/******/ .then(
/******/ function(result) {
/******/ deferred.resolve(result);
/******/ },
/******/ function(err) {
/******/ deferred.reject(err);
/******/ }
/******/ );
/******/ } else {
/******/ var outdatedModules = [];
/******/ for (var id in hotUpdate) {
/******/ if (Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
/******/ outdatedModules.push(toModuleId(id));
/******/ }
/******/ }
/******/ deferred.resolve(outdatedModules);
/******/ }
/******/ }
/******/
/******/ function hotApply(options) {
/******/ if (hotStatus !== "ready")
/******/ throw new Error("apply() is only allowed in ready status");
/******/ options = options || {};
/******/
/******/ var cb;
/******/ var i;
/******/ var j;
/******/ var module;
/******/ var moduleId;
/******/
/******/ function getAffectedStuff(updateModuleId) {
/******/ var outdatedModules = [updateModuleId];
/******/ var outdatedDependencies = {};
/******/
/******/ var queue = outdatedModules.slice().map(function(id) {
/******/ return {
/******/ chain: [id],
/******/ id: id
/******/ };
/******/ });
/******/ while (queue.length > 0) {
/******/ var queueItem = queue.pop();
/******/ var moduleId = queueItem.id;
/******/ var chain = queueItem.chain;
/******/ module = installedModules[moduleId];
/******/ if (!module || module.hot._selfAccepted) continue;
/******/ if (module.hot._selfDeclined) {
/******/ return {
/******/ type: "self-declined",
/******/ chain: chain,
/******/ moduleId: moduleId
/******/ };
/******/ }
/******/ if (module.hot._main) {
/******/ return {
/******/ type: "unaccepted",
/******/ chain: chain,
/******/ moduleId: moduleId
/******/ };
/******/ }
/******/ for (var i = 0; i < module.parents.length; i++) {
/******/ var parentId = module.parents[i];
/******/ var parent = installedModules[parentId];
/******/ if (!parent) continue;
/******/ if (parent.hot._declinedDependencies[moduleId]) {
/******/ return {
/******/ type: "declined",
/******/ chain: chain.concat([parentId]),
/******/ moduleId: moduleId,
/******/ parentId: parentId
/******/ };
/******/ }
/******/ if (outdatedModules.indexOf(parentId) !== -1) continue;
/******/ if (parent.hot._acceptedDependencies[moduleId]) {
/******/ if (!outdatedDependencies[parentId])
/******/ outdatedDependencies[parentId] = [];
/******/ addAllToSet(outdatedDependencies[parentId], [moduleId]);
/******/ continue;
/******/ }
/******/ delete outdatedDependencies[parentId];
/******/ outdatedModules.push(parentId);
/******/ queue.push({
/******/ chain: chain.concat([parentId]),
/******/ id: parentId
/******/ });
/******/ }
/******/ }
/******/
/******/ return {
/******/ type: "accepted",
/******/ moduleId: updateModuleId,
/******/ outdatedModules: outdatedModules,
/******/ outdatedDependencies: outdatedDependencies
/******/ };
/******/ }
/******/
/******/ function addAllToSet(a, b) {
/******/ for (var i = 0; i < b.length; i++) {
/******/ var item = b[i];
/******/ if (a.indexOf(item) === -1) a.push(item);
/******/ }
/******/ }
/******/
/******/ // at begin all updates modules are outdated
/******/ // the "outdated" status can propagate to parents if they don't accept the children
/******/ var outdatedDependencies = {};
/******/ var outdatedModules = [];
/******/ var appliedUpdate = {};
/******/
/******/ var warnUnexpectedRequire = function warnUnexpectedRequire() {
/******/ console.warn(
/******/ "[HMR] unexpected require(" + result.moduleId + ") to disposed module"
/******/ );
/******/ };
/******/
/******/ for (var id in hotUpdate) {
/******/ if (Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
/******/ moduleId = toModuleId(id);
/******/ /** @type {TODO} */
/******/ var result;
/******/ if (hotUpdate[id]) {
/******/ result = getAffectedStuff(moduleId);
/******/ } else {
/******/ result = {
/******/ type: "disposed",
/******/ moduleId: id
/******/ };
/******/ }
/******/ /** @type {Error|false} */
/******/ var abortError = false;
/******/ var doApply = false;
/******/ var doDispose = false;
/******/ var chainInfo = "";
/******/ if (result.chain) {
/******/ chainInfo = "\nUpdate propagation: " + result.chain.join(" -> ");
/******/ }
/******/ switch (result.type) {
/******/ case "self-declined":
/******/ if (options.onDeclined) options.onDeclined(result);
/******/ if (!options.ignoreDeclined)
/******/ abortError = new Error(
/******/ "Aborted because of self decline: " +
/******/ result.moduleId +
/******/ chainInfo
/******/ );
/******/ break;
/******/ case "declined":
/******/ if (options.onDeclined) options.onDeclined(result);
/******/ if (!options.ignoreDeclined)
/******/ abortError = new Error(
/******/ "Aborted because of declined dependency: " +
/******/ result.moduleId +
/******/ " in " +
/******/ result.parentId +
/******/ chainInfo
/******/ );
/******/ break;
/******/ case "unaccepted":
/******/ if (options.onUnaccepted) options.onUnaccepted(result);
/******/ if (!options.ignoreUnaccepted)
/******/ abortError = new Error(
/******/ "Aborted because " + moduleId + " is not accepted" + chainInfo
/******/ );
/******/ break;
/******/ case "accepted":
/******/ if (options.onAccepted) options.onAccepted(result);
/******/ doApply = true;
/******/ break;
/******/ case "disposed":
/******/ if (options.onDisposed) options.onDisposed(result);
/******/ doDispose = true;
/******/ break;
/******/ default:
/******/ throw new Error("Unexception type " + result.type);
/******/ }
/******/ if (abortError) {
/******/ hotSetStatus("abort");
/******/ return Promise.reject(abortError);
/******/ }
/******/ if (doApply) {
/******/ appliedUpdate[moduleId] = hotUpdate[moduleId];
/******/ addAllToSet(outdatedModules, result.outdatedModules);
/******/ for (moduleId in result.outdatedDependencies) {
/******/ if (
/******/ Object.prototype.hasOwnProperty.call(
/******/ result.outdatedDependencies,
/******/ moduleId
/******/ )
/******/ ) {
/******/ if (!outdatedDependencies[moduleId])
/******/ outdatedDependencies[moduleId] = [];
/******/ addAllToSet(
/******/ outdatedDependencies[moduleId],
/******/ result.outdatedDependencies[moduleId]
/******/ );
/******/ }
/******/ }
/******/ }
/******/ if (doDispose) {
/******/ addAllToSet(outdatedModules, [result.moduleId]);
/******/ appliedUpdate[moduleId] = warnUnexpectedRequire;
/******/ }
/******/ }
/******/ }
/******/
/******/ // Store self accepted outdated modules to require them later by the module system
/******/ var outdatedSelfAcceptedModules = [];
/******/ for (i = 0; i < outdatedModules.length; i++) {
/******/ moduleId = outdatedModules[i];
/******/ if (
/******/ installedModules[moduleId] &&
/******/ installedModules[moduleId].hot._selfAccepted
/******/ )
/******/ outdatedSelfAcceptedModules.push({
/******/ module: moduleId,
/******/ errorHandler: installedModules[moduleId].hot._selfAccepted
/******/ });
/******/ }
/******/
/******/ // Now in "dispose" phase
/******/ hotSetStatus("dispose");
/******/ Object.keys(hotAvailableFilesMap).forEach(function(chunkId) {
/******/ if (hotAvailableFilesMap[chunkId] === false) {
/******/ hotDisposeChunk(chunkId);
/******/ }
/******/ });
/******/
/******/ var idx;
/******/ var queue = outdatedModules.slice();
/******/ while (queue.length > 0) {
/******/ moduleId = queue.pop();
/******/ module = installedModules[moduleId];
/******/ if (!module) continue;
/******/
/******/ var data = {};
/******/
/******/ // Call dispose handlers
/******/ var disposeHandlers = module.hot._disposeHandlers;
/******/ for (j = 0; j < disposeHandlers.length; j++) {
/******/ cb = disposeHandlers[j];
/******/ cb(data);
/******/ }
/******/ hotCurrentModuleData[moduleId] = data;
/******/
/******/ // disable module (this disables requires from this module)
/******/ module.hot.active = false;
/******/
/******/ // remove module from cache
/******/ delete installedModules[moduleId];
/******/
/******/ // when disposing there is no need to call dispose handler
/******/ delete outdatedDependencies[moduleId];
/******/
/******/ // remove "parents" references from all children
/******/ for (j = 0; j < module.children.length; j++) {
/******/ var child = installedModules[module.children[j]];
/******/ if (!child) continue;
/******/ idx = child.parents.indexOf(moduleId);
/******/ if (idx >= 0) {
/******/ child.parents.splice(idx, 1);
/******/ }
/******/ }
/******/ }
/******/
/******/ // remove outdated dependency from module children
/******/ var dependency;
/******/ var moduleOutdatedDependencies;
/******/ for (moduleId in outdatedDependencies) {
/******/ if (
/******/ Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)
/******/ ) {
/******/ module = installedModules[moduleId];
/******/ if (module) {
/******/ moduleOutdatedDependencies = outdatedDependencies[moduleId];
/******/ for (j = 0; j < moduleOutdatedDependencies.length; j++) {
/******/ dependency = moduleOutdatedDependencies[j];
/******/ idx = module.children.indexOf(dependency);
/******/ if (idx >= 0) module.children.splice(idx, 1);
/******/ }
/******/ }
/******/ }
/******/ }
/******/
/******/ // Not in "apply" phase
/******/ hotSetStatus("apply");
/******/
/******/ hotCurrentHash = hotUpdateNewHash;
/******/
/******/ // insert new code
/******/ for (moduleId in appliedUpdate) {
/******/ if (Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {
/******/ modules[moduleId] = appliedUpdate[moduleId];
/******/ }
/******/ }
/******/
/******/ // call accept handlers
/******/ var error = null;
/******/ for (moduleId in outdatedDependencies) {
/******/ if (
/******/ Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)
/******/ ) {
/******/ module = installedModules[moduleId];
/******/ if (module) {
/******/ moduleOutdatedDependencies = outdatedDependencies[moduleId];
/******/ var callbacks = [];
/******/ for (i = 0; i < moduleOutdatedDependencies.length; i++) {
/******/ dependency = moduleOutdatedDependencies[i];
/******/ cb = module.hot._acceptedDependencies[dependency];
/******/ if (cb) {
/******/ if (callbacks.indexOf(cb) !== -1) continue;
/******/ callbacks.push(cb);
/******/ }
/******/ }
/******/ for (i = 0; i < callbacks.length; i++) {
/******/ cb = callbacks[i];
/******/ try {
/******/ cb(moduleOutdatedDependencies);
/******/ } catch (err) {
/******/ if (options.onErrored) {
/******/ options.onErrored({
/******/ type: "accept-errored",
/******/ moduleId: moduleId,
/******/ dependencyId: moduleOutdatedDependencies[i],
/******/ error: err
/******/ });
/******/ }
/******/ if (!options.ignoreErrored) {
/******/ if (!error) error = err;
/******/ }
/******/ }
/******/ }
/******/ }
/******/ }
/******/ }
/******/
/******/ // Load self accepted modules
/******/ for (i = 0; i < outdatedSelfAcceptedModules.length; i++) {
/******/ var item = outdatedSelfAcceptedModules[i];
/******/ moduleId = item.module;
/******/ hotCurrentParents = [moduleId];
/******/ try {
/******/ __webpack_require__(moduleId);
/******/ } catch (err) {
/******/ if (typeof item.errorHandler === "function") {
/******/ try {
/******/ item.errorHandler(err);
/******/ } catch (err2) {
/******/ if (options.onErrored) {
/******/ options.onErrored({
/******/ type: "self-accept-error-handler-errored",
/******/ moduleId: moduleId,
/******/ error: err2,
/******/ originalError: err
/******/ });
/******/ }
/******/ if (!options.ignoreErrored) {
/******/ if (!error) error = err2;
/******/ }
/******/ if (!error) error = err;
/******/ }
/******/ } else {
/******/ if (options.onErrored) {
/******/ options.onErrored({
/******/ type: "self-accept-errored",
/******/ moduleId: moduleId,
/******/ error: err
/******/ });
/******/ }
/******/ if (!options.ignoreErrored) {
/******/ if (!error) error = err;
/******/ }
/******/ }
/******/ }
/******/ }
/******/
/******/ // handle errors in accept handlers and self accepted module load
/******/ if (error) {
/******/ hotSetStatus("fail");
/******/ return Promise.reject(error);
/******/ }
/******/
/******/ hotSetStatus("idle");
/******/ return new Promise(function(resolve) {
/******/ resolve(outdatedModules);
/******/ });
/******/ }
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // Promise = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "runtime~main": 0
/******/ };
/******/
/******/ var deferredModules = [];
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {},
/******/ hot: hotCreateModule(moduleId),
/******/ parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
/******/ children: []
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/";
/******/
/******/ // __webpack_hash__
/******/ __webpack_require__.h = function() { return hotCurrentHash; };
/******/
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ jsonpArray.push = webpackJsonpCallback;
/******/ jsonpArray = jsonpArray.slice();
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ // run deferred modules from other chunks
/******/ checkDeferredModules();
/******/ })
/************************************************************************/
/******/ ([]);
//# sourceMappingURL=bundle.js.map

1
testdata/react_with_loop/dist/out/bundle.js.map поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

190
testdata/react_with_loop/dist/out/main.chunk.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,190 @@
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["main"],{
/***/ "./src/App.js":
/*!********************!*\
!*** ./src/App.js ***!
\********************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/classCallCheck */ "./node_modules/@babel/runtime/helpers/esm/classCallCheck.js");
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_createClass__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/createClass */ "./node_modules/@babel/runtime/helpers/esm/createClass.js");
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_possibleConstructorReturn__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/possibleConstructorReturn */ "./node_modules/@babel/runtime/helpers/esm/possibleConstructorReturn.js");
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/getPrototypeOf */ "./node_modules/@babel/runtime/helpers/esm/getPrototypeOf.js");
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_inherits__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/inherits */ "./node_modules/@babel/runtime/helpers/esm/inherits.js");
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_5__);
/* harmony import */ var _Counter__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./Counter */ "./src/Counter.jsx");
/* harmony import */ var _Running__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./Running */ "./src/Running.js");
var App =
/*#__PURE__*/
function (_Component) {
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_inherits__WEBPACK_IMPORTED_MODULE_4__["default"])(App, _Component);
function App() {
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__["default"])(this, App);
return Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_possibleConstructorReturn__WEBPACK_IMPORTED_MODULE_2__["default"])(this, Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__["default"])(App).apply(this, arguments));
}
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_createClass__WEBPACK_IMPORTED_MODULE_1__["default"])(App, [{
key: "render",
value: function render() {
return react__WEBPACK_IMPORTED_MODULE_5___default.a.createElement(_Counter__WEBPACK_IMPORTED_MODULE_6__["Counter"], null);
}
}]);
return App;
}(react__WEBPACK_IMPORTED_MODULE_5__["Component"]);
Object(_Running__WEBPACK_IMPORTED_MODULE_7__["runForever"])();
/* harmony default export */ __webpack_exports__["default"] = (App);
/***/ }),
/***/ "./src/Counter.jsx":
/*!*************************!*\
!*** ./src/Counter.jsx ***!
\*************************/
/*! exports provided: Counter */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Counter", function() { return Counter; });
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/classCallCheck */ "./node_modules/@babel/runtime/helpers/esm/classCallCheck.js");
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_createClass__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/createClass */ "./node_modules/@babel/runtime/helpers/esm/createClass.js");
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_possibleConstructorReturn__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/possibleConstructorReturn */ "./node_modules/@babel/runtime/helpers/esm/possibleConstructorReturn.js");
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/getPrototypeOf */ "./node_modules/@babel/runtime/helpers/esm/getPrototypeOf.js");
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_inherits__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/inherits */ "./node_modules/@babel/runtime/helpers/esm/inherits.js");
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_5__);
var Counter =
/*#__PURE__*/
function (_Component) {
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_inherits__WEBPACK_IMPORTED_MODULE_4__["default"])(Counter, _Component);
function Counter(props) {
var _this;
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__["default"])(this, Counter);
_this = Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_possibleConstructorReturn__WEBPACK_IMPORTED_MODULE_2__["default"])(this, Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__["default"])(Counter).call(this, props));
_this.state = {
count: 0
};
return _this;
}
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_createClass__WEBPACK_IMPORTED_MODULE_1__["default"])(Counter, [{
key: "increment",
value: function increment() {
var newval = this.state.count + 1;
this.setState({
count: newval
});
this.loop();
}
}, {
key: "render",
value: function render() {
var _this2 = this;
return react__WEBPACK_IMPORTED_MODULE_5___default.a.createElement("div", {
className: "shopping-list"
}, "Click count = ", this.state.count, ";", react__WEBPACK_IMPORTED_MODULE_5___default.a.createElement("div", null, react__WEBPACK_IMPORTED_MODULE_5___default.a.createElement("button", {
id: "incrementBtn",
onClick: function onClick() {
return _this2.increment();
}
}, "Increment"), " "));
}
}, {
key: "loop",
value: function loop() {
for (var iterationNumber = 1; iterationNumber < 200; ++iterationNumber) {
console.log("starting iteration: ".concat(iterationNumber));
var squared = iterationNumber * iterationNumber;
console.log("ending iteration: ".concat(iterationNumber, " squared: ").concat(squared));
}
}
}]);
return Counter;
}(react__WEBPACK_IMPORTED_MODULE_5__["Component"]);
/***/ }),
/***/ "./src/Running.js":
/*!************************!*\
!*** ./src/Running.js ***!
\************************/
/*! exports provided: runForever */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runForever", function() { return runForever; });
// a script to keep running forever
var num = 0;
function runForever() {
setTimeout(function () {
num++;
runForever();
}, 50);
}
/***/ }),
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js");
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _App__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./App */ "./src/App.js");
react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_App__WEBPACK_IMPORTED_MODULE_2__["default"], null), document.getElementById('root'));
/***/ }),
/***/ 0:
/*!****************************!*\
!*** multi ./src/index.js ***!
\****************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(/*! C:\ws\vscode-chrome-debug\testdata\react_with_loop\src\index.js */"./src/index.js");
/***/ })
},[[0,"runtime~main",0]]]);
//# sourceMappingURL=main.chunk.js.map

1
testdata/react_with_loop/dist/out/main.chunk.js.map поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
{"version":3,"sources":["webpack:///./src/App.js","webpack:///./src/Counter.jsx","webpack:///./src/Running.js","webpack:///./src/index.js"],"names":["App","Component","runForever","Counter","props","state","count","newval","setState","loop","increment","iterationNumber","console","log","squared","num","setTimeout","ReactDOM","render","document","getElementById"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;;IAEMA,G;;;;;;;;;;;;;6BACK;AACP,aACE,2DAAC,gDAAD,OADF;AAGD;;;;EALeC,+C;;AAQlBC,2DAAU;AAEKF,kEAAf,E;;;;;;;;;;;;;;;;;;;;;;;;;;ACbA;AAEO,IAAMG,OAAb;AAAA;AAAA;AAAA;;AAEE,mBAAYC,KAAZ,EAAmB;AAAA;;AAAA;;AACjB,iWAAMA,KAAN;AAEA,UAAKC,KAAL,GAAa;AACXC,WAAK,EAAE;AADI,KAAb;AAHiB;AAMlB;;AARH;AAAA;AAAA,gCAUgB;AACV,UAAMC,MAAM,GAAG,KAAKF,KAAL,CAAWC,KAAX,GAAmB,CAAlC;AACA,WAAKE,QAAL,CAAc;AAAEF,aAAK,EAAEC;AAAT,OAAd;AACA,WAAKE,IAAL;AACD;AAdL;AAAA;AAAA,6BAgBa;AAAA;;AACP,aACE;AAAK,iBAAS,EAAC;AAAf,2BACiB,KAAKJ,KAAL,CAAWC,KAD5B,OAEE,wEACE;AAAQ,UAAE,EAAC,cAAX;AAA0B,eAAO,EAAG;AAAA,iBAAM,MAAI,CAACI,SAAL,EAAN;AAAA;AAApC,qBADF,MAFF,CADF;AAQD;AAzBL;AAAA;AAAA,2BA2BW;AACL,WAAK,IAAIC,eAAe,GAAG,CAA3B,EAA8BA,eAAe,GAAG,GAAhD,EAAqD,EAAEA,eAAvD,EAAwE;AACtEC,eAAO,CAACC,GAAR,+BAAmCF,eAAnC;AACA,YAAMG,OAAO,GAAGH,eAAe,GAAGA,eAAlC;AACAC,eAAO,CAACC,GAAR,6BAAiCF,eAAjC,uBAA6DG,OAA7D;AACD;AACF;AAjCL;;AAAA;AAAA,EAA6Bb,+CAA7B,E;;;;;;;;;;;;ACFA;AAAA;AAAA;AAEA,IAAIc,GAAG,GAAG,CAAV;AACO,SAASb,UAAT,GAAsB;AACzBc,YAAU,CAAC,YAAM;AACbD,OAAG;AACHb,cAAU;AACb,GAHS,EAGP,EAHO,CAAV;AAIH,C;;;;;;;;;;;;ACTD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEAe,gDAAQ,CAACC,MAAT,CAAgB,2DAAC,4CAAD,OAAhB,EAAyBC,QAAQ,CAACC,cAAT,CAAwB,MAAxB,CAAzB,E","file":"out/main.chunk.js","sourcesContent":["import React, { Component } from 'react';\r\nimport { Counter } from './Counter';\r\nimport { runForever } from './Running';\r\n\r\nclass App extends Component {\r\n render() {\r\n return (\r\n <Counter ></Counter>\r\n );\r\n }\r\n}\r\n\r\nrunForever();\r\n\r\nexport default App;\r\n","\r\nimport React, { Component } from 'react';\r\n\r\nexport class Counter extends Component {\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.state = {\r\n count: 0\r\n };\r\n }\r\n\r\n increment() {\r\n const newval = this.state.count + 1 ;\r\n this.setState({ count: newval });\r\n this.loop();\r\n }\r\n\r\n render() {\r\n return (\r\n <div className=\"shopping-list\">\r\n Click count = {this.state.count};\r\n <div>\r\n <button id=\"incrementBtn\" onClick={ () => this.increment() } >Increment</button> { }\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n loop() {\r\n for (let iterationNumber = 1; iterationNumber < 200; ++iterationNumber) {\r\n console.log(`starting iteration: ${iterationNumber}`);\r\n const squared = iterationNumber * iterationNumber;\r\n console.log(`ending iteration: ${iterationNumber} squared: ${squared}`);\r\n }\r\n }\r\n }\r\n\r\n","\r\n// a script to keep running forever\r\n\r\nlet num = 0;\r\nexport function runForever() {\r\n setTimeout(() => {\r\n num++;\r\n runForever();\r\n }, 50);\r\n}","import React from 'react';\r\nimport ReactDOM from 'react-dom';\r\nimport App from './App';\r\n\r\nReactDOM.render(<App />, document.getElementById('root'));\r\n"],"sourceRoot":""}

15
testdata/react_with_loop/dist/src/App.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,15 @@
import React, { Component } from 'react';
import { Counter } from './Counter';
import { runForever } from './Running';
class App extends Component {
render() {
return (
<Counter ></Counter>
);
}
}
runForever();
export default App;

9
testdata/react_with_loop/dist/src/App.test.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

39
testdata/react_with_loop/dist/src/Counter.jsx поставляемый Normal file
Просмотреть файл

@ -0,0 +1,39 @@
import React, { Component } from 'react';
export class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment() {
const newval = this.state.count + 1 ;
this.setState({ count: newval });
this.loop();
}
render() {
return (
<div className="shopping-list">
Click count = {this.state.count};
<div>
<button id="incrementBtn" onClick={ () => this.increment() } >Increment</button> { }
</div>
</div>
);
}
loop() {
for (let iterationNumber = 1; iterationNumber < 200; ++iterationNumber) {
console.log(`starting iteration: ${iterationNumber}`);
const squared = iterationNumber * iterationNumber;
console.log(`ending iteration: ${iterationNumber} squared: ${squared}`);
}
}
}

10
testdata/react_with_loop/dist/src/Running.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,10 @@
// a script to keep running forever
let num = 0;
export function runForever() {
setTimeout(() => {
num++;
runForever();
}, 50);
}

5
testdata/react_with_loop/dist/src/index.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

16620
testdata/react_with_loop/package-lock.json сгенерированный поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

123
testdata/react_with_loop/package.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,123 @@
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/core": "7.2.2",
"@svgr/webpack": "4.1.0",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "9.0.0",
"babel-jest": "23.6.0",
"babel-loader": "8.0.5",
"babel-plugin-named-asset-import": "^0.3.1",
"babel-preset-react-app": "^7.0.2",
"bfj": "6.1.1",
"case-sensitive-paths-webpack-plugin": "2.2.0",
"css-loader": "1.0.0",
"dotenv": "6.0.0",
"dotenv-expand": "4.2.0",
"eslint": "5.12.0",
"eslint-config-react-app": "^3.0.8",
"eslint-loader": "2.1.1",
"eslint-plugin-flowtype": "2.50.1",
"eslint-plugin-import": "2.14.0",
"eslint-plugin-jsx-a11y": "6.1.2",
"eslint-plugin-react": "7.12.4",
"file-loader": "2.0.0",
"fs-extra": "7.0.1",
"html-webpack-plugin": "4.0.0-alpha.2",
"identity-obj-proxy": "3.0.0",
"jest": "23.6.0",
"jest-pnp-resolver": "1.0.2",
"jest-resolve": "23.6.0",
"jest-watch-typeahead": "^0.2.1",
"mini-css-extract-plugin": "0.5.0",
"optimize-css-assets-webpack-plugin": "5.0.1",
"pnp-webpack-plugin": "1.2.1",
"postcss-flexbugs-fixes": "4.1.0",
"postcss-loader": "3.0.0",
"postcss-preset-env": "6.5.0",
"postcss-safe-parser": "4.0.1",
"react": "^16.8.4",
"react-app-polyfill": "^0.2.2",
"react-dev-utils": "^8.0.0",
"react-dom": "^16.8.4",
"resolve": "1.10.0",
"sass-loader": "7.1.0",
"style-loader": "0.23.1",
"terser-webpack-plugin": "1.2.2",
"url-loader": "1.1.2",
"webpack": "4.28.3",
"webpack-dev-server": "3.1.14",
"webpack-manifest-plugin": "2.0.4",
"workbox-webpack-plugin": "3.6.3"
},
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts"
],
"resolver": "jest-pnp-resolver",
"setupFiles": [
"react-app-polyfill/jsdom"
],
"testMatch": [
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/?(*.)(spec|test).{js,jsx,ts,tsx}"
],
"testEnvironment": "jsdom",
"testURL": "http://localhost",
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
"^.+\\.module\\.(css|sass|scss)$"
],
"moduleNameMapper": {
"^react-native$": "react-native-web",
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
},
"moduleFileExtensions": [
"web.js",
"js",
"web.ts",
"ts",
"web.tsx",
"tsx",
"json",
"web.jsx",
"jsx",
"node"
],
"watchPlugins": [
"C:\\development\\react_test_project\\my-app\\node_modules\\jest-watch-typeahead\\filename.js",
"C:\\development\\react_test_project\\my-app\\node_modules\\jest-watch-typeahead\\testname.js"
]
},
"babel": {
"presets": [
"react-app"
]
},
"devDependencies": {
"clean-webpack-plugin": "^2.0.0",
"copy-webpack-plugin": "^5.0.1"
}
}

Двоичные данные
testdata/react_with_loop/public/favicon.ico поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.8 KiB

37
testdata/react_with_loop/public/index.html поставляемый Normal file
Просмотреть файл

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

15
testdata/react_with_loop/public/manifest.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

193
testdata/react_with_loop/scripts/build.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,193 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const path = require('path');
const chalk = require('react-dev-utils/chalk');
const fs = require('fs-extra');
const webpack = require('webpack');
const bfj = require('bfj');
const configFactory = require('../config/webpack.config');
const paths = require('../config/paths');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError');
const measureFileSizesBeforeBuild =
FileSizeReporter.measureFileSizesBeforeBuild;
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
const useYarn = fs.existsSync(paths.yarnLockFile);
// These sizes are pretty large. We'll warn for bundles exceeding them.
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
// Process CLI arguments
const argv = process.argv.slice(2);
const writeStatsJson = argv.indexOf('--stats') !== -1;
// Generate configuration
const config = configFactory('development');
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
return measureFileSizesBeforeBuild(paths.appBuild);
})
.then(previousFileSizes => {
// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
fs.emptyDirSync(paths.appBuild);
// Merge with the public folder
copyPublicFolder();
// Start the webpack build
return build(previousFileSizes);
})
.then(
({ stats, previousFileSizes, warnings }) => {
if (warnings.length) {
console.log(chalk.yellow('Compiled with warnings.\n'));
console.log(warnings.join('\n\n'));
console.log(
'\nSearch for the ' +
chalk.underline(chalk.yellow('keywords')) +
' to learn more about each warning.'
);
console.log(
'To ignore, add ' +
chalk.cyan('// eslint-disable-next-line') +
' to the line before.\n'
);
} else {
console.log(chalk.green('Compiled successfully.\n'));
}
// console.log('File sizes after gzip:\n');
// printFileSizesAfterBuild(
// stats,
// previousFileSizes,
// paths.appBuild,
// WARN_AFTER_BUNDLE_GZIP_SIZE,
// WARN_AFTER_CHUNK_GZIP_SIZE
// );
// console.log();
const appPackage = require(paths.appPackageJson);
const publicUrl = paths.publicUrl;
const publicPath = config.output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appBuild);
printHostingInstructions(
appPackage,
publicUrl,
publicPath,
buildFolder,
useYarn
);
},
err => {
console.log(chalk.red('Failed to compile.\n'));
printBuildError(err);
process.exit(1);
}
)
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});
// Create the production build and print the deployment instructions.
function build(previousFileSizes) {
console.log('Creating a nice debug build...');
let compiler = webpack(config);
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
let messages;
if (err) {
if (!err.message) {
return reject(err);
}
messages = formatWebpackMessages({
errors: [err.message],
warnings: [],
});
} else {
messages = formatWebpackMessages(
stats.toJson({ all: false, warnings: true, errors: true })
);
}
if (messages.errors.length) {
// Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise.
if (messages.errors.length > 1) {
messages.errors.length = 1;
}
return reject(new Error(messages.errors.join('\n\n')));
}
if (
process.env.CI &&
(typeof process.env.CI !== 'string' ||
process.env.CI.toLowerCase() !== 'false') &&
messages.warnings.length
) {
console.log(
chalk.yellow(
'\nTreating warnings as errors because process.env.CI = true.\n' +
'Most CI servers set it automatically.\n'
)
);
return reject(new Error(messages.warnings.join('\n\n')));
}
const resolveArgs = {
stats,
previousFileSizes,
warnings: messages.warnings,
};
if (writeStatsJson) {
return bfj
.write(paths.appBuild + '/bundle-stats.json', stats.toJson())
.then(() => resolve(resolveArgs))
.catch(error => reject(new Error(error)));
}
return resolve(resolveArgs);
});
});
}
function copyPublicFolder() {
fs.copySync(paths.appPublic, paths.appBuild, {
dereference: true,
filter: file => file !== paths.appHtml,
});
}

132
testdata/react_with_loop/scripts/start.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,132 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const fs = require('fs');
const chalk = require('react-dev-utils/chalk');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const clearConsole = require('react-dev-utils/clearConsole');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const {
choosePort,
createCompiler,
prepareProxy,
prepareUrls,
} = require('react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('react-dev-utils/openBrowser');
const paths = require('../config/paths');
const configFactory = require('../config/webpack.config');
const createDevServerConfig = require('../config/webpackDevServer.config');
const useYarn = fs.existsSync(paths.yarnLockFile);
const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
// Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || '0.0.0.0';
if (process.env.HOST) {
console.log(
chalk.cyan(
`Attempting to bind to HOST environment variable: ${chalk.yellow(
chalk.bold(process.env.HOST)
)}`
)
);
console.log(
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
);
console.log(
`Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}`
);
console.log();
}
// We require that you explictly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `choosePort()` Promise resolves to the next free port.
return choosePort(HOST, DEFAULT_PORT);
})
.then(port => {
if (port == null) {
// We have not found a port.
return;
}
const config = configFactory('development');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name;
const useTypeScript = fs.existsSync(paths.appTsConfig);
const urls = prepareUrls(protocol, HOST, port);
const devSocket = {
warnings: warnings =>
devServer.sockWrite(devServer.sockets, 'warnings', warnings),
errors: errors =>
devServer.sockWrite(devServer.sockets, 'errors', errors),
};
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler({
appName,
config,
devSocket,
urls,
useYarn,
useTypeScript,
webpack,
});
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
// Serve webpack assets generated by the compiler over a web server.
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);
const devServer = new WebpackDevServer(compiler, serverConfig);
// Launch WebpackDevServer.
devServer.listen(port, HOST, err => {
if (err) {
return console.log(err);
}
if (isInteractive) {
clearConsole();
}
console.log(chalk.cyan('Starting the development server...\n'));
openBrowser(urls.localUrlForBrowser);
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
devServer.close();
process.exit();
});
});
})
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});

60
testdata/react_with_loop/scripts/test.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,60 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'test';
process.env.NODE_ENV = 'test';
process.env.PUBLIC_URL = '';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const jest = require('jest');
const execSync = require('child_process').execSync;
let argv = process.argv.slice(2);
function isInGitRepository() {
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
function isInMercurialRepository() {
try {
execSync('hg --cwd . root', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
// Watch unless on CI, in coverage mode, explicitly adding `--no-watch`,
// or explicitly running all tests
if (
!process.env.CI &&
argv.indexOf('--coverage') === -1 &&
argv.indexOf('--no-watch') === -1 &&
argv.indexOf('--watchAll') === -1
) {
// https://github.com/facebook/create-react-app/issues/5210
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
argv.push(hasSourceControl ? '--watch' : '--watchAll');
}
// Jest doesn't have this option so we'll remove it
if (argv.indexOf('--no-watch') !== -1) {
argv = argv.filter(arg => arg !== '--no-watch');
}
jest.run(argv);

15
testdata/react_with_loop/src/App.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,15 @@
import React, { Component } from 'react';
import { Counter } from './Counter';
import { runForever } from './Running';
class App extends Component {
render() {
return (
<Counter ></Counter>
);
}
}
runForever();
export default App;

9
testdata/react_with_loop/src/App.test.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

39
testdata/react_with_loop/src/Counter.jsx поставляемый Normal file
Просмотреть файл

@ -0,0 +1,39 @@
import React, { Component } from 'react';
export class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment() {
const newval = this.state.count + 1 ;
this.setState({ count: newval });
this.loop();
}
render() {
return (
<div className="shopping-list">
Click count = {this.state.count};
<div>
<button id="incrementBtn" onClick={ () => this.increment() } >Increment</button> { }
</div>
</div>
);
}
loop() {
for (let iterationNumber = 1; iterationNumber < 200; ++iterationNumber) {
console.log(`starting iteration: ${iterationNumber}`);
const squared = iterationNumber * iterationNumber;
console.log(`ending iteration: ${iterationNumber} squared: ${squared}`);
}
}
}

10
testdata/react_with_loop/src/Running.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,10 @@
// a script to keep running forever
let num = 0;
export function runForever() {
setTimeout(() => {
num++;
runForever();
}, 50);
}

5
testdata/react_with_loop/src/index.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

10
testdata/stackTrace/app.js поставляемый
Просмотреть файл

@ -1,9 +1,13 @@
function buttonClick() {
setTimeout(inner, 100);
setTimeout(timeoutCallback, 100);
}
function inner() {
function timeoutCallback() {
eval("evalCallback();");
}
function evalCallback() {
(function() {
console.log('Inside anonymous function'); // bpLabel: stackTraceBreakpoint
console.log('Test stack trace here');
})();
}

16
testdata/variablesScopes/.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}",
// "debugServer": 4712
}
]
}

83
testdata/variablesScopes/blockScope/blockVariablesApp.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,83 @@
(function () {
console.log('TestCode: START');
let i;
for (i = 7; i < 10; ++i) {
console.log('TestCode: BEFORE-ADDING-VARIABLES');
// Try to create a variable of each important type, to verify that we can see their contents properly
const globalCode = 'page loaded';
function consoleDotLog(m) {
console.log(m)
}
const manyPropsObj = { prop2: 'abc', prop1: 'def' };
for (let i = 0; i <= 100; i++) manyPropsObj[i] = 2 * i + 1;
let r = /^asdf.*$/g;
let longStr = `this is a
string with
newlines`;
let element = document.body;
const buffer = new ArrayBuffer(8);
let buffView = new Int32Array(buffer);
buffView[0] = 234;
let s = Symbol('hi');
let e = new Error('hi');
let m = new Map();
m.set('a', 1);
let b = document.body;
const nan = NaN;
let inf = 1 / 0;
let infStr = "Infinity";
// These 3 are going to be global variables
eval('let evalVar3 = [1,2,3]');
eval('let evalVar1 = 16');
eval('let evalVar2 = "sdlfk"');
let bool = true;
const fn = () => {
// Some fn
let xyz = 321;
anotherFn();
};
let fn2 = function () {
let zzz = 333;
};
let qqq;
let str = 'hello';
let xyz = 1;
let obj = { a: 2, get thing() { throw 'xyz'; }, set thing(x) { } };
xyz++; xyz++; xyz++;
let myVar = {
num: 1,
str: "Global",
obj: {
obj: {
obj: { num: 10 },
obj2: { obj3: {} },
}
},
obj2: {
obj3: {}
},
}
myVar["self"] = myVar;
myVar.obj["toMyVar"] = myVar;
console.log('TestCode: BEFORE-VERIFYING-VARIABLES');
debugger; // Pause here to verify that we can see the values and types of all the variables
break;
}
console.log('TestCode: END');
})();

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше