diff --git a/src/taco-dependency-installer/package.json b/src/taco-dependency-installer/package.json
index 153e33fe..472f8517 100644
--- a/src/taco-dependency-installer/package.json
+++ b/src/taco-dependency-installer/package.json
@@ -35,7 +35,8 @@
"should": "4.3.0",
"taco-tests-utils": "file:../taco-tests-utils",
"typescript": "~1.6.2",
- "mockery": "~1.4.0"
+ "mockery": "~1.4.0",
+ "mock-fs": "^3.4.0"
},
"license": "MIT"
}
diff --git a/src/taco-dependency-installer/test/androidSdkInstaller.ts b/src/taco-dependency-installer/test/androidSdkInstaller.ts
index 6a263a91..78574b90 100644
--- a/src/taco-dependency-installer/test/androidSdkInstaller.ts
+++ b/src/taco-dependency-installer/test/androidSdkInstaller.ts
@@ -11,6 +11,10 @@
///
///
///
+///
+///
+///
+///
"use strict";
@@ -23,6 +27,8 @@ var shouldModule = require("should");
import installerProtocol = require("../elevatedInstallerProtocol");
import ILogger = installerProtocol.ILogger;
import mockery = require("mockery");
+import mockFs = require("mock-fs");
+import path = require("path");
import Q = require("q");
import tacoTestsUtils = require("taco-tests-utils");
import _ = require("lodash");
@@ -50,64 +56,95 @@ class FakeLogger implements ILogger {
}
describe("AndroidSdkInstaller telemetry", () => {
+ // Parameters for AndroidSdkInstaller
+ var steps: DependencyInstallerInterfaces.IStepsDeclaration;
+ var installerInfo: DependencyInstallerInterfaces.IInstallerData = {
+ installSource: "",
+ sha1: "",
+ bytes: 0,
+ installDestination: "",
+ steps: steps
+ };
+ var softwareVersion: string = "";
+ var installTo: string = "C:\\Program Files (x86)\\Android"; // Default installation directory in windows
+
+ // Mocks used by the tests
+ var mockPath: typeof path;
+ var fakeTelemetryHelper: TacoTestsUtils.TelemetryFakes.Helper;
+ var fakeProcess: nodeFakes.Process;
+ var androidSdkInstallerClass: any;
+ var childProcessModule: nodeFakes.ChildProcessModule;
+
before(() => {
// We tell mockery to replace "require()" with our own custom mock objects
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
+
+ fakeProcess = new nodeFakes.Process()
+ .fakeDeterministicHrtime();
+
+ var fakeProcessUtilsModule = { ProcessUtils: fakeProcess.buildProcessUtils() };
+
+ mockery.registerMock("./processUtils", fakeProcessUtilsModule); // TelemetryHelper loads ./processUtils
+ var tacoUtils: typeof TacoUtility = require("taco-utils");
+ tacoUtils.Telemetry.init("TACO/dependencyInstaller", "1.2.3", false);
+
+ // Register mocks. child_process and taco-utils mocks needs to be registered before
+ // AndroidSdkInstaller is required for the mocking to work
+ childProcessModule = new nodeFakes.ChildProcessModule().fakeAllExecCallsEndingWithErrors();
+ mockery.registerMock("child_process", childProcessModule);
+
+ // Reload taco-tests-utils but now with the fake processUtils loaded, so the fake telemetry will use the fake process
+ var tacoTestsUtilsWithMocks: typeof tacoTestsUtils = require("taco-tests-utils");
+
+ fakeTelemetryHelper = new tacoTestsUtilsWithMocks.TelemetryFakes.Helper();
+ var tacoUtilsWithFakes = _.extend({}, tacoUtils, { TelemetryHelper: fakeTelemetryHelper, HasFakes: true },
+ fakeProcessUtilsModule);
+ mockery.registerMock("taco-utils", tacoUtilsWithFakes); // AndroidSdkInstaller loads taco-utils
+
+ // We need to mock path if we want to run windows tests on a mac, so it'll use ; as path delimiter
+ mockPath = _.extend({}, path);
+ mockery.registerMock("path", mockPath); // installerUtils uses path.delimiter, and it breaks the Windows tests on mac if not
+
+ // We require the AndroidSdkInstaller file, which will use all the mocked dependencies
+ androidSdkInstallerClass = require("../installers/androidSdkInstaller");
});
after(() => {
// Clean up and revert everything back to normal
mockery.deregisterAll();
mockery.disable();
+ mockFs.restore();
});
+ beforeEach(() => {
+ fakeTelemetryHelper.clear(); // So we'll only get the new events in each scenario
+ steps = { download: false, install: false, updateVariables: false, postInstall: false }; // We reset all the steps to false
+ fakeProcess.clearEnv(); // Reset environment variables, given that we modify some of them in the tests
+ });
+
+ function telemetryGeneratedShouldBe(expectedTelemetry: TacoUtility.ICommandTelemetryProperties[],
+ expectedMessagePattern: RegExp, done: MochaDone): Q.Promise {
+ var androidSdkInstaller = new androidSdkInstallerClass(installerInfo, softwareVersion, installTo,
+ new FakeLogger(), steps);
+
+ return androidSdkInstaller.run()
+ .then(() => Q.reject(new Error("Should have gotten a rejection in this test")), (error: Error) => {
+ return fakeTelemetryHelper.getAllSentEvents().then((allSentEvents: TelemetryEvent[]) => {
+ // We check the message first, because some coding defects can make the tests end in unexpected states
+ error.message.should.match(expectedMessagePattern);
+
+ // Then we validate the telemetry
+ allSentEvents.should.eql(expectedTelemetry);
+ });
+ }).done(done, done);
+ }
+
describe("updateVariablesDarwin", () => {
it("generates telemetry if there is an error on the update command", (done: MochaDone) => {
- var fakeProcessUtilsModule = {
- ProcessUtils: new nodeFakes.Process().fakeMacOS()
- .fakeDeterministicHrtime().buildProcessUtils()
- };
- mockery.registerMock("./processUtils", fakeProcessUtilsModule); // TelemetryHelper loads ./processUtils
- var tacoUtils: typeof TacoUtility = require("taco-utils");
- tacoUtils.Telemetry.init("TACO/dependencyInstaller", "1.2.3", false);
+ fakeProcess.fakeMacOS();
+ steps.updateVariables = true; // We only test this step on this test
- // Register mocks. child_process and taco-utils mocks needs to be register before
- // AndroidSdkInstaller is required for the mock to work
- mockery.registerMock("child_process", new nodeFakes.ChildProcessModule().fakeAllExecCallsEndingWithErrors());
-
- // Reload taco-tests-utils but now with the fake processUtils loaded, so the fake telemetry will use the fake process
- var tacoTestsUtilsWithMocks: typeof tacoTestsUtils = require("taco-tests-utils");
-
- var fakeTelemetryHelper: TacoTestsUtils.TelemetryFakes.Helper = new tacoTestsUtilsWithMocks.TelemetryFakes.Helper();
- var tacoUtilsWithFakes = _.extend({}, tacoUtils, { TelemetryHelper: fakeTelemetryHelper, HasFakes: true },
- fakeProcessUtilsModule);
- mockery.registerMock("taco-utils", tacoUtilsWithFakes); // AndroidSdkInstaller loads taco-utils
-
- // We require the AndroidSdkInstaller file, which will use all the mocked dependencies
- var androidSdkInstallerClass = require("../installers/androidSdkInstaller");
-
- var steps: DependencyInstallerInterfaces.IStepsDeclaration = {
- download: false,
- install: false,
- updateVariables: true, // We only test this step on this test
- postInstall: false
- };
-
- var installerInfo: DependencyInstallerInterfaces.IInstallerData = {
- installSource: "",
- sha1: "",
- bytes: 0,
- installDestination: "",
- steps: steps
- };
-
- var softwareVersion: string = "";
- var installTo: string = "";
-
- var androidSdkInstaller = new androidSdkInstallerClass(installerInfo, softwareVersion, installTo,
- new FakeLogger(), steps);
-
- var expectedTelemetry = [
+ var expectedTelemetry: TacoUtility.ICommandTelemetryProperties[] = [
{
"initialStep.time": { isPii: false, value: "2000" },
step: { isPii: false, value: "initialStep" }
@@ -121,12 +158,193 @@ describe("AndroidSdkInstaller telemetry", () => {
}
];
- return androidSdkInstaller.run()
- .then(() => Q.reject(new Error("Should have gotten a rejection in this test")), () => {
- return fakeTelemetryHelper.getAllSentEvents().then((allSentEvents: TelemetryEvent[]) => {
- allSentEvents.should.eql(expectedTelemetry);
- });
- }).done(done, done);
+ return telemetryGeneratedShouldBe(expectedTelemetry, /Error while executing/, done);
+ });
+ });
+
+ describe("installation", () => {
+ it("generates telemetry error if there is no install location", (done: MochaDone) => {
+ fakeProcess.fakeMacOS();
+ steps.install = true; // We only test this step on this test
+ installTo = ""; // We don't have an install location
+
+ var expectedTelemetry: TacoUtility.ICommandTelemetryProperties[] = [
+ {
+ "initialStep.time": { isPii: false, value: "2000" },
+ step: { isPii: false, value: "initialStep" }
+ },
+ {
+ "error.description": { isPii: false, value: "NeedInstallDestination on installDefault" },
+ "install.time": { isPii: false, value: "2000" },
+ lastStepExecuted: { isPii: false, value: "install" },
+ step: { isPii: false, value: "install" },
+ time: { isPii: false, value: "3000" }
+ }];
+
+ return telemetryGeneratedShouldBe(expectedTelemetry, /NeedInstallDestination/, done);
+ });
+ });
+
+ describe("post-installation in mac", () => {
+ it("generates telemetry error if we can't give executable permissions to the android executable", (done: MochaDone) => {
+ fakeProcess.fakeMacOS();
+
+ steps.postInstall = true; // We only test this step on this test
+ steps.updateVariables = true; // We need this step because post-install uses this.androidHomeValue populated in this step
+
+ // child_process.exec will succeed while setting the path, and fail while giving permissions
+ childProcessModule.fakeUsingCommandToDetermineResult((command: string) => /export PATH=/.test(command),
+ (command: string) => /chmod a\+x/.test(command));
+
+ var filePath = path.join(fakeProcess.env.HOME, ".bash_profile");
+ var files: mockFs.Config = {};
+ files[filePath] = "";
+ mockFs(files);
+
+ var expectedTelemetry: TacoUtility.ICommandTelemetryProperties[] = [
+ {
+ "initialStep.time": { isPii: false, value: "2000" },
+ step: { isPii: false, value: "initialStep" }
+ },
+ {
+ step: { isPii: false, value: "updateVariables" },
+ "updateVariables.time": { isPii: false, value: "1000" }
+ },
+ {
+ "error.description": {
+ isPii: false,
+ value: "ErrorOnChildProcess on addExecutePermission"
+ },
+ lastStepExecuted: { isPii: false, value: "postInstall" },
+ "postInstall.time": { isPii: false, value: "2000" },
+ step: { isPii: false, value: "postInstall" },
+ time: { isPii: false, value: "5000" }
+ }
+ ];
+
+ return telemetryGeneratedShouldBe(expectedTelemetry, /Error while executing/g, done);
+ });
+ });
+
+ describe("post-installation in windows", () => {
+ beforeEach(() => {
+ fakeProcess.fakeWindows();
+ mockPath.delimiter = ";"; // Installer utils uses this, and the path logic breaks in Mac if we don't mock it
+
+ steps.postInstall = true; // We only test this step on this test
+ steps.updateVariables = true; // We need this step because post-install uses this.androidHomeValue populated in this step
+
+ // Set ANDROID_HOME so updateVariables won't try to re-set it
+ installTo = "C:\\Program Files (x86)\\Android"; // We don't have an install location
+ var androidHomePath = fakeProcess.env.ANDROID_HOME = mockPath.join(installTo, "android-sdk-windows");
+
+ // Set android paths in the PATH so we won't have to add them
+ var platformToolsPath = mockPath.join(androidHomePath, "platform-tools");
+ var androidToolsPath = mockPath.join(androidHomePath, "tools");
+ fakeProcess.env.PATH = platformToolsPath + mockPath.delimiter + androidToolsPath;
+ });
+
+ it("installAndroidPackages generates telemetry error if the command used generates errors in stderr", (done: MochaDone) => {
+ var spawnErrorMessage = "Couldn't find the Android update command executable";
+ childProcessModule.mockSpawn.setDefault(
+ childProcessModule.mockSpawn.simple(/*exitCode*/ 0, /*stdout*/ "", /*stderr*/ spawnErrorMessage));
+
+ var expectedTelemetry: TacoUtility.ICommandTelemetryProperties[] = [
+ {
+ "initialStep.time": { isPii: false, value: "2000" },
+ step: { isPii: false, value: "initialStep" }
+ },
+ {
+ step: { isPii: false, value: "updateVariables" },
+ "updateVariables.time": { isPii: false, value: "1000" }
+ },
+ {
+ "error.code": { isPii: false, value: "0" },
+ "error.description": {
+ isPii: false,
+ value: "ErrorOnExitOfChildProcess on postInstallDefault"
+ },
+ "error.message": {
+ isPii: true,
+ value: "Couldn't find the Android update command executable"
+ },
+ lastStepExecuted: { isPii: false, value: "postInstall" },
+ "postInstall.time": { isPii: false, value: "2000" },
+ step: { isPii: false, value: "postInstall" },
+ time: { isPii: false, value: "5000" }
+ }
+ ];
+
+ return telemetryGeneratedShouldBe(expectedTelemetry, new RegExp(spawnErrorMessage), done);
+ });
+
+ it("installAndroidPackages generates telemetry error if the command used is invalid", (done: MochaDone) => {
+ var spawnErrorMessage = "The command is not recognized by the system";
+ childProcessModule.mockSpawn.setDefault(function (callback: Function): void {
+ /* Warning: The "this" in the next line is the one passed by mockSpawn library. For that
+ to work this context needs to be a JavaScript lambda function. Do not convert this to
+ an arrow function, or this will break because of the change in semantics. */
+ this.emit("error", new Error(spawnErrorMessage)); // invokes childProcess.on('error')
+ setTimeout(() => callback(8), 0); // Then the child process ends with an arbitrary error code of 8
+ });
+
+ var expectedTelemetry: TacoUtility.ICommandTelemetryProperties[] = [
+ {
+ "initialStep.time": { isPii: false, value: "2000" },
+ step: { isPii: false, value: "initialStep" }
+ },
+ {
+ step: { isPii: false, value: "updateVariables" },
+ "updateVariables.time": { isPii: false, value: "1000" }
+ },
+ {
+ "error.description": {
+ isPii: false,
+ value: "ErrorOnChildProcess on postInstallDefault"
+ },
+ lastStepExecuted: { isPii: false, value: "postInstall" },
+ "postInstall.time": { isPii: false, value: "2000" },
+ step: { isPii: false, value: "postInstall" },
+ time: { isPii: false, value: "5000" }
+ }
+ ];
+
+ return telemetryGeneratedShouldBe(expectedTelemetry, new RegExp(spawnErrorMessage), done);
+ });
+
+ it("killAdb generates telemetry error if the command used is invalid", (done: MochaDone) => {
+ // First call to install the android packages will succeed
+ childProcessModule.mockSpawn.sequence.add(childProcessModule.mockSpawn.simple(/*exitCode*/ 0, /*stdout*/ "", /*stderr*/ ""));
+
+ // Second call to kill adb will fail
+ var spawnErrorMessage = "The kill adb command is not recognized by the system";
+ childProcessModule.mockSpawn.sequence.add(function (callback: Function): void {
+ /* Warning: The "this" in the next line is the one passed by mockSpawn library. For that
+ to work this context needs to be a JavaScript lambda function. Do not convert this to
+ an arrow function, or this will break because of the change in semantics. */
+ this.emit("error", new Error(spawnErrorMessage)); // invokes childProcess.on('error')
+ setTimeout(() => callback(8), 0); // Then the child process ends with an arbitrary error code of 8
+ });
+
+ var expectedTelemetry: TacoUtility.ICommandTelemetryProperties[] = [
+ {
+ "initialStep.time": { isPii: false, value: "2000" },
+ step: { isPii: false, value: "initialStep" }
+ },
+ {
+ step: { isPii: false, value: "updateVariables" },
+ "updateVariables.time": { isPii: false, value: "1000" }
+ },
+ {
+ "error.description": { isPii: false, value: "ErrorOnKillingAdb in killAdb" },
+ lastStepExecuted: { isPii: false, value: "postInstall" },
+ "postInstall.time": { isPii: false, value: "2000" },
+ step: { isPii: false, value: "postInstall" },
+ time: { isPii: false, value: "5000" }
+ }
+ ];
+
+ return telemetryGeneratedShouldBe(expectedTelemetry, new RegExp(spawnErrorMessage), done);
});
});
});
diff --git a/src/taco-dependency-installer/utils/installerUtils.ts b/src/taco-dependency-installer/utils/installerUtils.ts
index 9c834ed6..287b8aa6 100644
--- a/src/taco-dependency-installer/utils/installerUtils.ts
+++ b/src/taco-dependency-installer/utils/installerUtils.ts
@@ -41,7 +41,7 @@ module InstallerUtils {
}
class InstallerUtils {
- private static pathName: string = os.platform() === "win32" ? "Path" : "PATH";
+ private static PATH_NAME: string = "PATH"; // *nix uses uppercase and it's case sensitive. Windows is case insensitive
/**
* Verifies if the specified file is valid by comparing its sha1 signature and its size in bytes with the provided expectedSha1 and expectedBytes.
*
@@ -119,8 +119,8 @@ class InstallerUtils {
public static promptUser(message: string): Q.Promise {
var deferred: Q.Deferred = Q.defer();
var rl: readline.ReadLine = readline.createInterface({
- input: process.stdin,
- output: process.stdout
+ input: tacoUtils.ProcessUtils.getProcess().stdin,
+ output: tacoUtils.ProcessUtils.getProcess().stdout
});
rl.question(message, function (answer: string): void {
@@ -143,9 +143,9 @@ class InstallerUtils {
* @return {Q.Promise} A promise resolved with a boolean indicating whether the specified environment variable must be set
*/
public static mustSetSystemVariable(name: string, value: string, logger: ILogger): Q.Promise {
- if (!process.env[name]) {
+ if (!tacoUtils.ProcessUtils.getProcess().env[name]) {
return Q.resolve(true);
- } else if (path.resolve(utils.expandEnvironmentVariables(process.env[name])) === path.resolve(utils.expandEnvironmentVariables(value))) {
+ } else if (path.resolve(utils.expandEnvironmentVariables(tacoUtils.ProcessUtils.getProcess().env[name])) === path.resolve(utils.expandEnvironmentVariables(value))) {
// If this environment variable is already defined, but it is already set to what we need, we don't need to set it again
return Q.resolve(false);
}
@@ -197,7 +197,7 @@ class InstallerUtils {
*
* @return {boolean} A boolean set to true if the Path system variable already contains the specified value in one of its segments
*/
- public static pathContains(valueToCheck: string, pathValue: string = process.env[InstallerUtils.pathName]): boolean {
+ public static pathContains(valueToCheck: string, pathValue: string = tacoUtils.ProcessUtils.getProcess().env[InstallerUtils.PATH_NAME]): boolean {
if (!pathValue) {
return false;
}
diff --git a/src/taco-dependency-installer/utils/win32/installerUtilsWin32.ts b/src/taco-dependency-installer/utils/win32/installerUtilsWin32.ts
index 371b5a2c..2067bbc1 100644
--- a/src/taco-dependency-installer/utils/win32/installerUtilsWin32.ts
+++ b/src/taco-dependency-installer/utils/win32/installerUtilsWin32.ts
@@ -28,7 +28,7 @@ class InstallerUtilsWin32 {
*
* @param {string} name The name of the environment variable to set
* @param {string} value The desired value for the specified environment variable
- * @param {InstallerProtocol.ILogger} logger The logger for the current process
+ * @param {InstallerProtocol.ILogger} logger The logger for the process
*
* @return {Q.Promise} A promise resolved with an empty object if the operation succeeds, or rejected with the appropriate error if not
*/
@@ -52,8 +52,8 @@ class InstallerUtilsWin32 {
* @return {Q.Promise} A promise resolved with an empty object if the operation succeeds, or rejected with the appropriate error if not
*/
public static addToPathIfNeededWin32(addToPath: string[]): Q.Promise {
- var pathName: string = "Path";
- var pathValue: string = process.env[pathName];
+ var pathName: string = "PATH"; // Windows is case-insensitive. We use uppercase to be more compatible with *nix systems
+ var pathValue: string = tacoUtils.ProcessUtils.getProcess().env[pathName];
addToPath.forEach(function (value: string): void {
if (!installerUtils.pathContains(value)) {
@@ -61,7 +61,7 @@ class InstallerUtilsWin32 {
}
});
- if (pathValue === process.env[pathName]) {
+ if (pathValue === tacoUtils.ProcessUtils.getProcess().env[pathName]) {
return Q.resolve({});
}
@@ -78,13 +78,13 @@ class InstallerUtilsWin32 {
* @return {Q.Promise} A promise resolved with an empty object if the operation succeeds, or rejected with the appropriate error if not
*/
public static setEnvironmentVariableWin32(name: string, value: string): Q.Promise {
- if (process.platform !== "win32") {
+ if (tacoUtils.ProcessUtils.getProcess().platform !== "win32") {
// No-op for platforms other than win32
return Q.resolve({});
}
// Set variable for this running process
- process.env[name] = value;
+ tacoUtils.ProcessUtils.getProcess().env[name] = value;
// Set variable for the system
var scriptPath: string = path.resolve(__dirname, "setSystemVariable.ps1");
diff --git a/src/taco-tests-utils/nodeFakes.ts b/src/taco-tests-utils/nodeFakes.ts
index b99e906e..dc31250d 100644
--- a/src/taco-tests-utils/nodeFakes.ts
+++ b/src/taco-tests-utils/nodeFakes.ts
@@ -17,9 +17,18 @@
import _ = require("lodash");
+/* tslint:disable:no-var-requires */
+/* We don't have a .d.ts file for mock-spawn */
+var mockSpawnModule = require("mock-spawn");
+/* tslint:enable:no-var-requires */
+
export module NodeFakes {
export interface IEnvironmentVariables {
+ // We should add here the environment variables that we use in TACO. Remember to also add them in the .d.ts file
HOME?: string;
+ ANDROID_HOME?: string;
+ PATH?: string;
+ TACO_UNIT_TEST?: string;
}
export type IChildProcess = NodeJSChildProcess.ChildProcess;
@@ -30,6 +39,8 @@ export module NodeFakes {
export type ExecSecondArgument = IExecOptions | Callback;
+ export type CommandTester = (command: string) => boolean;
+
export type ExecFileOptions = {
cwd?: string; stdio?: any; customFds?: any; env?: any;
encoding?: string; timeout?: number; maxBuffer?: string; killSignal?: string;
@@ -68,7 +79,7 @@ export module NodeFakes {
public env: IEnvironmentVariables;
constructor() {
- this.env = _.extend({}, process.env);
+ this.clearEnv();
}
public asProcess(): NodeJS.Process {
@@ -109,7 +120,12 @@ export module NodeFakes {
public fakeWindows(): Process {
var username = "my_username";
this.asProcess().platform = "win32";
- // this.asProcess().env.HOME = "C:\\Users\\" + username;
+ this.asProcess().env.HOME = "C:\\Users\\" + username;
+ return this;
+ }
+
+ public clearEnv(): Process {
+ this.env = { TACO_UNIT_TEST: "true" };
return this;
}
}
@@ -175,27 +191,42 @@ export module NodeFakes {
}
export class ChildProcessModule /* implements typeof NodeJSChildProcess*/ {
- /** Methods to configure the fake process **/
+ /** mock spawn variable. Docs at https://www.npmjs.com/package/mock-spawn **/
+ public mockSpawn: any = mockSpawnModule();
+
+ // We simulate that all calls to exect are succesfull
+ public fakeAllExecCallsSucceed(): ChildProcessModule {
+ this.exec = (command: string, optionsOrCallback: ExecSecondArgument, callback: Callback = null): IChildProcess => {
+ return this.callCallback(command, optionsOrCallback, callback, true);
+ };
+ return this;
+ }
// We simulate that all calls to exect end with an error
public fakeAllExecCallsEndingWithErrors(): ChildProcessModule {
+ this.exec = (command: string, optionsOrCallback: ExecSecondArgument, callback: Callback = null): IChildProcess => {
+ return this.callCallback(command, optionsOrCallback, callback, false);
+ };
+ return this;
+ }
+
+ public fakeUsingCommandToDetermineResult(successFilter: CommandTester, failureFilter: CommandTester): ChildProcessModule {
this.exec = (command: string, optionsOrCallback: ExecSecondArgument, callback?: Callback): IChildProcess => {
- var realCallback = (callback || optionsOrCallback);
+ var isSuccess: boolean = successFilter(command);
+ var isFailure: boolean = failureFilter(command);
- // We call the callback in an async way
- setTimeout(() => {
- realCallback(new Error("Error while executing " + command), /*stdout*/ new Buffer(""), /*stderr*/ new Buffer(""));
- }, 0);
-
- return new ChildProcess();
+ if (isSuccess !== isFailure) {
+ return this.callCallback(command, optionsOrCallback, callback, isSuccess);
+ } else {
+ throw new Error("A command should be exactly a success, or a failure. It can't be both nor either.\n"
+ + "Command: " + command + " isSuccess: " + isSuccess + " isFailure: " + isFailure);
+ }
};
return this;
}
public spawn(command: string, args?: string[], options?: SpawnOptions): IChildProcess {
- /* TODO: We should consider integrating this method with this library https://www.npmjs.com/package/mock-spawn
- if we need to mock spawn */
- throw this.notImplementedError();
+ return this.mockSpawn(command, args, options);
}
public exec(command: string, options: IExecOptions, callback: Callback): IChildProcess;
@@ -215,6 +246,17 @@ export module NodeFakes {
throw this.notImplementedError();
}
+ private callCallback(command: string, optionsOrCallback: ExecSecondArgument,
+ callback: Callback, wasSuccessful: boolean): ChildProcess {
+ var realCallback = (callback || optionsOrCallback);
+ // We call the callback in an async way
+ var error = wasSuccessful ? null : new Error("Error while executing " + command);
+ setTimeout(() => {
+ realCallback(error, /*stdout*/ new Buffer(""), /*stderr*/ new Buffer(""));
+ }, 0);
+ return new ChildProcess();
+ }
+
private notImplementedError(): Error {
return Error("This method hasn't been implemented for this test");
}
diff --git a/src/taco-tests-utils/package.json b/src/taco-tests-utils/package.json
index 257dfb13..19039622 100644
--- a/src/taco-tests-utils/package.json
+++ b/src/taco-tests-utils/package.json
@@ -18,7 +18,8 @@
"main": "tacoTestsUtils.js",
"dependencies": {
"lodash": "^3.10.1",
- "q": "~1.4.1"
+ "q": "~1.4.1",
+ "mock-spawn": "~0.2.6"
},
"devDependencies": {
"typescript": "~1.6.2"
diff --git a/src/taco-tests-utils/telemetryFakes.ts b/src/taco-tests-utils/telemetryFakes.ts
index 35a71e22..cc52b1a0 100644
--- a/src/taco-tests-utils/telemetryFakes.ts
+++ b/src/taco-tests-utils/telemetryFakes.ts
@@ -66,5 +66,9 @@ export module TelemetryFakes {
() => generator.time(null, () => codeGeneratingTelemetry(generator)),
() => generator.sendAndNotify()); // After
}
+
+ public clear(): void {
+ this.telemetryGenerators = [];
+ }
}
}
diff --git a/src/taco-utils/resourceManager.ts b/src/taco-utils/resourceManager.ts
index f9f582bd..d7768af9 100644
--- a/src/taco-utils/resourceManager.ts
+++ b/src/taco-utils/resourceManager.ts
@@ -13,8 +13,9 @@ import fs = require ("fs");
import path = require ("path");
import argsHelper = require ("./argsHelper");
+import processUtils = require("./processUtils");
+import resourceSet = require("./resourceSet");
import tacoGlobalConfig = require ("./tacoGlobalConfig");
-import resourceSet = require ("./resourceSet");
import ArgsHelper = argsHelper.ArgsHelper;
import TacoGlobalConfig = tacoGlobalConfig.TacoGlobalConfig;
@@ -95,7 +96,7 @@ module TacoUtility {
var args: string[] = ArgsHelper.getOptionalArgsArrayFromFunctionCall(arguments, 1);
var result: string = this.getStringForLocale(this.bestLanguageMatch(this.getCurrentLocale()), id, args);
- if (result && process.env["TACO_UNIT_TEST"]) {
+ if (result && processUtils.ProcessUtils.getProcess().env["TACO_UNIT_TEST"]) {
// Mock out resources for consistency in unit tests, but only if they exist
return id;
} else {
diff --git a/src/typings/mock-fs.d.ts b/src/typings/mock-fs.d.ts
new file mode 100644
index 00000000..7b167bc3
--- /dev/null
+++ b/src/typings/mock-fs.d.ts
@@ -0,0 +1,51 @@
+// Type definitions for mock-fs 2.5.0
+// Project: https://github.com/tschaub/mock-fs
+// Definitions by: Wim Looman
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+///
+
+declare module "mock-fs" {
+ import fs = require("fs");
+
+ function mock(config?: mock.Config): void;
+
+ module mock {
+ function file(config: FileConfig): File;
+ function directory(config: DirectoryConfig): Directory;
+ function symlink(config: SymlinkConfig): Symlink;
+
+ function restore(): void;
+
+ function fs(config?: Config): typeof fs;
+
+ interface Config {
+ [path: string]: string | Buffer | File | Directory | Symlink | Config;
+ }
+
+ interface CommonConfig {
+ mode?: number;
+ uid?: number;
+ git?: number;
+ atime?: Date;
+ ctime?: Date;
+ mtime?: Date;
+ }
+
+ interface FileConfig extends CommonConfig {
+ content: string | Buffer;
+ }
+ interface DirectoryConfig extends CommonConfig {
+ items: Config;
+ }
+ interface SymlinkConfig extends CommonConfig {
+ path: string;
+ }
+
+ class File { private _file: any; }
+ class Directory { private _directory: any; }
+ class Symlink { private _symlink: any; }
+ }
+
+ export = mock;
+}
diff --git a/src/typings/nodeFakes.d.ts b/src/typings/nodeFakes.d.ts
index 40d5ed20..24b065f7 100644
--- a/src/typings/nodeFakes.d.ts
+++ b/src/typings/nodeFakes.d.ts
@@ -11,11 +11,16 @@
declare module TacoTestsUtils {
export module NodeFakes {
interface IEnvironmentVariables {
+ // We should add here the environment variables that we use in TACO. Remember to also add them in the .ts file
HOME?: string;
+ ANDROID_HOME?: string;
+ PATH?: string;
+ TACO_UNIT_TEST?: string;
}
type IChildProcess = NodeJSChildProcess.ChildProcess;
type IExecOptions = NodeJSChildProcess.IExecOptions;
type Callback = (error: Error, stdout: Buffer, stderr: Buffer) => void;
+ type CommandTester = (command: string) => boolean;
type ExecSecondArgument = IExecOptions | Callback;
type ExecFileOptions = {
cwd?: string;
@@ -83,14 +88,16 @@ declare module TacoTestsUtils {
getProcess(): NodeJS.Process;
}
class Process {
- env: IEnvironmentVariables;
- constructor();
- asProcess(): NodeJS.Process;
- buildProcessUtils(): IProcessUtils;
+ public env: IEnvironmentVariables;
+ public constructor();
+ public asProcess(): NodeJS.Process;
+ public buildProcessUtils(): IProcessUtils;
+
/** Methods to configure the fake process **/
- fakeDeterministicHrtime(): Process;
- fakeMacOS(): Process;
- fakeWindows(): Process;
+ public fakeDeterministicHrtime(): Process;
+ public fakeMacOS(): Process;
+ public fakeWindows(): Process;
+ public clearEnv(): Process;
}
abstract class EventEmitter implements NodeJS.EventEmitter {
protected abstract notImplementedError(): Error;
@@ -115,7 +122,17 @@ declare module TacoTestsUtils {
}
class ChildProcessModule {
/** Methods to configure the fake process **/
+ // This method will make all child_process.exec call succeed
+ fakeAllExecCallsSucceed(): ChildProcessModule;
+ // This method will make all child_process.exec call fail
fakeAllExecCallsEndingWithErrors(): ChildProcessModule;
+ // This method will make some child_process.exec call fail, and some succeed based on the lambda tests passed
+ fakeUsingCommandToDetermineResult(successFilter: CommandTester, failureFilter: CommandTester): ChildProcessModule;
+
+ /** mock spawn variable. Docs at https://www.npmjs.com/package/mock-spawn **/
+ public mockSpawn: any; // This is an instance of mockSpawn();
+
+ /** child_process methods **/
spawn(command: string, args?: string[], options?: SpawnOptions): IChildProcess;
exec(command: string, options: IExecOptions, callback: Callback): IChildProcess;
exec(command: string, callback: Callback): IChildProcess;
diff --git a/src/typings/tacoTestsUtils.ts b/src/typings/tacoTestsUtils.d.ts
similarity index 100%
rename from src/typings/tacoTestsUtils.ts
rename to src/typings/tacoTestsUtils.d.ts
diff --git a/src/typings/telemetryFakes.d.ts b/src/typings/telemetryFakes.d.ts
index 0ecf5697..1b0d7fa6 100644
--- a/src/typings/telemetryFakes.d.ts
+++ b/src/typings/telemetryFakes.d.ts
@@ -22,6 +22,7 @@ declare module TacoTestsUtils {
generate(componentName: string, codeGeneratingTelemetry: { (telemetry: TacoUtility.TelemetryGenerator): T; }): T;
// Warning: We have no way of rejecting this promise, so we might get a test timeout if the events are never sent
getAllSentEvents(): Q.Promise;
+ clear(): void;
}
}
}