microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.5.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

test/resources/processExecution/simulator.ts

183lines · modeblame

efebb488digeff10 years ago1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as assert from "assert";
5import * as child_process from "child_process";
6import * as Q from "q";
7
3c172a05Artem Egorov8 years ago8import {ISpawnResult, ChildProcess} from "../../../src/common/node/childProcess";
9import {PromiseUtil} from "../../../src/common/node/promise";
efebb488digeff10 years ago10
11import {IStdOutEvent, IStdErrEvent, IErrorEvent, IExitEvent, ICustomEvent} from "./recording";
12import * as recording from "./recording";
13import * as simulators from "../simulators/childProcess";
14
15export type IEventArguments = recording.IEventArguments;
16export type Recording = recording.Recording;
17
18export interface ISimulationResult {
19simulatedProcess: child_process.ChildProcess;
20simulationEnded: Q.Promise<void> | void;
21}
22
23/* The side effects definition has rule to identify when an event with side effects happened in the simulation,
24and the callback that must be called for the simulator to simulate that side-effect during the tests.
25e.g.: When the 'projectWasCreated' event happens, we call a callback to actually create the project */
26export interface ISideEffectsDefinition {
27beforeStart: () => Q.Promise<void>;
28outputBased: IOutputBasedSideEffectDefinition[];
29beforeSuccess: (stdout: string, stderr: string) => Q.Promise<void>;
30}
31
32type IOutputBasedSideEffectDefinition = IOutputSingleEventBasedSideEffectDefinition | IWholeOutputBasedSideEffectDefinition;
33
34// Side effects based on analyzing each stdout event individually
35export interface IOutputSingleEventBasedSideEffectDefinition {
36eventPattern: RegExp;
37action: () => Q.Promise<void>;
38}
39
40// Side effects based on analyzing the whole stdout of the recording
41export interface IWholeOutputBasedSideEffectDefinition {
42wholeOutputPattern: RegExp;
43action: () => Q.Promise<void>;
44}
45
46/* We use this class to replay the events that we captured from a real execution of a process, to get
47the best possible simulation of that processes for our tests */
48export class Simulator {
49private process = new simulators.ChildProcess(); // Fake child process where we'll simulate the events that are recorded
50
51private wholeOutputBasedDefinitions: IWholeOutputBasedSideEffectDefinition[];
52private outputEventBasedDefinitions: IOutputSingleEventBasedSideEffectDefinition[];
53
54private allSimulatedEvents: IEventArguments[] = [];
55
56private allStdout = ""; // All the stdout the recordings have generated so far
57private allStderr = ""; // All the stderr the recordings have generated so far
58
59constructor(private sideEffectsDefinition: ISideEffectsDefinition) {
60// We extract the whole output rules and the single event output rules into two different lists.
61this.outputEventBasedDefinitions = <IOutputSingleEventBasedSideEffectDefinition[]>this.sideEffectsDefinition.outputBased.filter(definition =>
62!this.isWholeOutputDefinition(definition));
63this.wholeOutputBasedDefinitions = <IWholeOutputBasedSideEffectDefinition[]>this.sideEffectsDefinition.outputBased.filter(definition =>
64this.isWholeOutputDefinition(definition));
65}
66
67/* Given that we use ChildProcess for spawning processes, we create this spawn method with a
68similar result, so it'll be easier for simulated/fake classes to behave similar to the real
69ChildProcess class when spawning a simulated process */
70public spawn(): ISpawnResult {
71const fakeChildProcessModule = <typeof child_process><any>{
72spawn: () => {
73return this.process;
74},
75};
76
6e7f90d8Daniel Lebu10 years ago77/* We call spawn to fill the ISpawnResult object appropiatedly. The command
efebb488digeff10 years ago78and the arguments don't affect that object, so we just pass an empty command and parameters */
6e7f90d8Daniel Lebu10 years ago79return new ChildProcess({ childProcess: fakeChildProcessModule }).spawn("", []);
efebb488digeff10 years ago80}
81
27710197Vladimir Kotikov8 years ago82public simulate(simRecording: Recording): Q.Promise<void> {
83assert(simRecording, "recording shouldn't be null");
efebb488digeff10 years ago84return this.sideEffectsDefinition.beforeStart().then(() => {
27710197Vladimir Kotikov8 years ago85return this.simulateAllEvents(simRecording.events);
efebb488digeff10 years ago86});
87}
88
89public simulateAllEvents(events: IEventArguments[]): Q.Promise<void> {
90return new PromiseUtil().reduce(events, (event: IEventArguments) => this.simulateSingleEvent(event));
91}
92
93public getAllSimulatedEvents(): IEventArguments[] {
94return this.allSimulatedEvents;
27710197Vladimir Kotikov8 years ago95}
efebb488digeff10 years ago96
97private isWholeOutputDefinition(definition: IOutputBasedSideEffectDefinition): boolean {
98return definition.hasOwnProperty("wholeOutputPattern");
99}
100
101private simulateOutputSideEffects(data: string, previousOutputLength: number): Q.Promise<void> {
102/* We store the applicable side effects with the index where they were applicable, so we execute the
103ones that were detected earlier in the recording first */
104const applicableSideEffectDefinitions: { index: number, definition: IOutputBasedSideEffectDefinition }[] = [];
105
106this.outputEventBasedDefinitions.forEach(definition => {
107const match = data.match(definition.eventPattern);
5c8365a6Artem Egorov8 years ago108if (match && match.index !== undefined) {
efebb488digeff10 years ago109applicableSideEffectDefinitions.push({
110index: previousOutputLength + match.index, // Index relative to the whole output
111definition: definition,
112});
113}
114});
115
116/* We add the elements that match the whole output to applicableSideEffectDefinitions, and we remove them
117from future iterations of wholeOutputBasedDefinitions so they won't be matched again. */
118this.wholeOutputBasedDefinitions = this.wholeOutputBasedDefinitions.filter(definition => {
119const match = this.allStdout.match(definition.wholeOutputPattern);
5c8365a6Artem Egorov8 years ago120if (match && match.index !== undefined) {
efebb488digeff10 years ago121applicableSideEffectDefinitions.push({
122index: match.index,
123definition: definition,
124});
125return false; // We've just matched the output. Remove it from future iterations of wholeOutputBasedDefinitions
126}
127
128return true; // We didn't match yet, keep it for future iterations of wholeOutputBasedDefinitions
129});
130
131// Sort by index, so the action matching the earlier text gets executed first
132applicableSideEffectDefinitions.sort((a, b) => a.index - b.index);
133
134return new PromiseUtil().reduce(applicableSideEffectDefinitions, definition => definition.definition.action());
135}
136
137private simulateSingleEvent(event: IEventArguments): Q.Promise<void> {
138/* TODO: Implement proper timing logic based on return Q.delay(event.at).then(() => {
139using sinon fake timers to simulate time passing by */
140return Q.delay(0).then(() => {
141this.allSimulatedEvents.push(event);
142const key = Object.keys(event).find(eventKey => eventKey !== "after"); // At the moment we are only using a single key/parameter per event
143let result = Q<void>(void 0);
144switch (key) {
145case "stdout": {
146const data = (<IStdOutEvent>event).stdout.data;
147const previousOutputLength = this.allStdout.length;
148this.allStdout += data;
149result = this.simulateOutputSideEffects(data, previousOutputLength).then(() => {
150this.process.stdout.emit("data", new Buffer(data));
151});
152break;
153}
154case "stderr": {
155const data = (<IStdErrEvent>event).stderr.data;
156this.allStderr += data;
157this.process.stderr.emit("data", new Buffer(data));
158break;
159}
160case "error":
161this.process.emit("error", (<IErrorEvent>event).error.error);
162break;
163case "exit":
164const code = (<IExitEvent>event).exit.code;
165
166let beforeFinishing = Q<void>(void 0);
167if (code === 0) {
168beforeFinishing = Q(this.sideEffectsDefinition.beforeSuccess(this.allStdout, this.allStderr));
169}
170
171result = beforeFinishing.then(() => {
172this.process.emit("exit", code);
173});
174break;
175case "custom":
176return (<ICustomEvent>event).custom.lambda();
177default:
178throw new Error(`Unknown event to simulate: ${key} from:\n\t${event}`);
179}
180return Q.resolve<void>(void 0);
181});
182}
183}