Merge pull request #91 from Microsoft/add_additional_tests_for_install_req_telemetry

Added full coverage tests for the telemetry code in AndroidSdkInstaller
This commit is contained in:
digeff 2015-10-23 17:12:08 -07:00
Родитель 8eac09a1e3 89b2f94e78
Коммит c3c1e28ed4
12 изменённых файлов: 422 добавлений и 86 удалений

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

@ -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"
}

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

@ -11,6 +11,10 @@
/// <reference path="../../typings/mockery.d.ts"/>
/// <reference path="../../typings/should.d.ts"/>
/// <reference path="../../typings/telemetryFakes.d.ts"/>
/// <reference path="../../typings/mock-fs.d.ts"/>
/// <reference path="../../typings/nodeFakes.d.ts"/>
/// <reference path="../../typings/lodash.d.ts"/>
/// <reference path="../../typings/tacoTestsUtils.d.ts"/>
"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 = <typeof path> _.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<any> {
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);
});
});
});

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

@ -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<string> {
var deferred: Q.Deferred<any> = Q.defer<any>();
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<boolean>} 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<boolean> {
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;
}

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

@ -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<any>} 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<any>} 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<any> {
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<any>} 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<any> {
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");

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

@ -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> (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> (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");
}

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

@ -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"

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

@ -66,5 +66,9 @@ export module TelemetryFakes {
() => generator.time(null, () => codeGeneratingTelemetry(generator)),
() => generator.sendAndNotify()); // After
}
public clear(): void {
this.telemetryGenerators = [];
}
}
}

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

@ -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 {

51
src/typings/mock-fs.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,51 @@
// Type definitions for mock-fs 2.5.0
// Project: https://github.com/tschaub/mock-fs
// Definitions by: Wim Looman <https://github.com/Nemo157>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
/// <reference path="./node.d.ts" />
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;
}

31
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;

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

1
src/typings/telemetryFakes.d.ts поставляемый
Просмотреть файл

@ -22,6 +22,7 @@ declare module TacoTestsUtils {
generate<T>(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<TacoUtility.ICommandTelemetryProperties[]>;
clear(): void;
}
}
}