microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
7b48f26eb8caae7ed9cc1a09249195dfed76fa76

Branches

Tags

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

Clone

HTTPS

Download ZIP

test/resources/processExecution/simulator.ts

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