microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
8a67e140aee58da792b1e9c1c17358a98d5a3704

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/test/resources/processExecution/simulator.ts

183lines · modecode

1// 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
8import {ISpawnResult, ChildProcess} from "../../../common/node/childProcess";
9import {PromiseUtil} from "../../../common/node/promise";
10
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 {
19 simulatedProcess: child_process.ChildProcess;
20 simulationEnded: 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,
24 and the callback that must be called for the simulator to simulate that side-effect during the tests.
25 e.g.: When the 'projectWasCreated' event happens, we call a callback to actually create the project */
26export interface ISideEffectsDefinition {
27 beforeStart: () => Q.Promise<void>;
28 outputBased: IOutputBasedSideEffectDefinition[];
29 beforeSuccess: (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 {
36 eventPattern: RegExp;
37 action: () => Q.Promise<void>;
38}
39
40// Side effects based on analyzing the whole stdout of the recording
41export interface IWholeOutputBasedSideEffectDefinition {
42 wholeOutputPattern: RegExp;
43 action: () => 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
47 the best possible simulation of that processes for our tests */
48export class Simulator {
49 private process = new simulators.ChildProcess(); // Fake child process where we'll simulate the events that are recorded
50
51 private wholeOutputBasedDefinitions: IWholeOutputBasedSideEffectDefinition[];
52 private outputEventBasedDefinitions: IOutputSingleEventBasedSideEffectDefinition[];
53
54 private allSimulatedEvents: IEventArguments[] = [];
55
56 private allStdout = ""; // All the stdout the recordings have generated so far
57 private allStderr = ""; // All the stderr the recordings have generated so far
58
59 constructor(private sideEffectsDefinition: ISideEffectsDefinition) {
60 // We extract the whole output rules and the single event output rules into two different lists.
61 this.outputEventBasedDefinitions = <IOutputSingleEventBasedSideEffectDefinition[]>this.sideEffectsDefinition.outputBased.filter(definition =>
62 !this.isWholeOutputDefinition(definition));
63 this.wholeOutputBasedDefinitions = <IWholeOutputBasedSideEffectDefinition[]>this.sideEffectsDefinition.outputBased.filter(definition =>
64 this.isWholeOutputDefinition(definition));
65 }
66
67 /* Given that we use ChildProcess for spawning processes, we create this spawn method with a
68 similar result, so it'll be easier for simulated/fake classes to behave similar to the real
69 ChildProcess class when spawning a simulated process */
70 public spawn(): ISpawnResult {
71 const fakeChildProcessModule = <typeof child_process><any>{
72 spawn: () => {
73 return this.process;
74 },
75 };
76
77 /* We call spawn to fill the ISpawnResult object appropiatedly. The command
78 and the arguments don't affect that object, so we just pass an empty command and parameters */
79 return new ChildProcess({ childProcess: fakeChildProcessModule }).spawn("", []);
80 }
81
82 public simulate(simRecording: Recording): Q.Promise<void> {
83 assert(simRecording, "recording shouldn't be null");
84 return this.sideEffectsDefinition.beforeStart().then(() => {
85 return this.simulateAllEvents(simRecording.events);
86 });
87 }
88
89 public simulateAllEvents(events: IEventArguments[]): Q.Promise<void> {
90 return new PromiseUtil().reduce(events, (event: IEventArguments) => this.simulateSingleEvent(event));
91 }
92
93 public getAllSimulatedEvents(): IEventArguments[] {
94 return this.allSimulatedEvents;
95 }
96
97 private isWholeOutputDefinition(definition: IOutputBasedSideEffectDefinition): boolean {
98 return definition.hasOwnProperty("wholeOutputPattern");
99 }
100
101 private 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
103 ones that were detected earlier in the recording first */
104 const applicableSideEffectDefinitions: { index: number, definition: IOutputBasedSideEffectDefinition }[] = [];
105
106 this.outputEventBasedDefinitions.forEach(definition => {
107 const match = data.match(definition.eventPattern);
108 if (match && match.index !== undefined) {
109 applicableSideEffectDefinitions.push({
110 index: previousOutputLength + match.index, // Index relative to the whole output
111 definition: definition,
112 });
113 }
114 });
115
116 /* We add the elements that match the whole output to applicableSideEffectDefinitions, and we remove them
117 from future iterations of wholeOutputBasedDefinitions so they won't be matched again. */
118 this.wholeOutputBasedDefinitions = this.wholeOutputBasedDefinitions.filter(definition => {
119 const match = this.allStdout.match(definition.wholeOutputPattern);
120 if (match && match.index !== undefined) {
121 applicableSideEffectDefinitions.push({
122 index: match.index,
123 definition: definition,
124 });
125 return false; // We've just matched the output. Remove it from future iterations of wholeOutputBasedDefinitions
126 }
127
128 return 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
132 applicableSideEffectDefinitions.sort((a, b) => a.index - b.index);
133
134 return new PromiseUtil().reduce(applicableSideEffectDefinitions, definition => definition.definition.action());
135 }
136
137 private simulateSingleEvent(event: IEventArguments): Q.Promise<void> {
138 /* TODO: Implement proper timing logic based on return Q.delay(event.at).then(() => {
139 using sinon fake timers to simulate time passing by */
140 return Q.delay(0).then(() => {
141 this.allSimulatedEvents.push(event);
142 const key = Object.keys(event).find(eventKey => eventKey !== "after"); // At the moment we are only using a single key/parameter per event
143 let result = Q<void>(void 0);
144 switch (key) {
145 case "stdout": {
146 const data = (<IStdOutEvent>event).stdout.data;
147 const previousOutputLength = this.allStdout.length;
148 this.allStdout += data;
149 result = this.simulateOutputSideEffects(data, previousOutputLength).then(() => {
150 this.process.stdout.emit("data", new Buffer(data));
151 });
152 break;
153 }
154 case "stderr": {
155 const data = (<IStdErrEvent>event).stderr.data;
156 this.allStderr += data;
157 this.process.stderr.emit("data", new Buffer(data));
158 break;
159 }
160 case "error":
161 this.process.emit("error", (<IErrorEvent>event).error.error);
162 break;
163 case "exit":
164 const code = (<IExitEvent>event).exit.code;
165
166 let beforeFinishing = Q<void>(void 0);
167 if (code === 0) {
168 beforeFinishing = Q(this.sideEffectsDefinition.beforeSuccess(this.allStdout, this.allStderr));
169 }
170
171 result = beforeFinishing.then(() => {
172 this.process.emit("exit", code);
173 });
174 break;
175 case "custom":
176 return (<ICustomEvent>event).custom.lambda();
177 default:
178 throw new Error(`Unknown event to simulate: ${key} from:\n\t${event}`);
179 }
180 return Q.resolve<void>(void 0);
181 });
182 }
183}
184