@hkvstore/taco-cli
Version:
taco-cli is a command-line interface for rapid Apache Cordova development (forked from Microsoft taco-cli)
397 lines (395 loc) • 21.9 kB
JavaScript
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
/// <reference path="../../typings/mocha.d.ts" />
/// <reference path="../../typings/node.d.ts" />
/// <reference path="../../typings/should.d.ts" />
/// <reference path="../../typings/cordovaExtensions.d.ts" />
/// <reference path="../../typings/del.d.ts" />
;
/* tslint:disable:no-var-requires */
// var require needed for should module to work correctly
// Note not import: We don't want to refer to shouldModule, but we need the require to occur since it modifies the prototype of Object.
var shouldModule = require("should");
/* tslint:enable:no-var-requires */
var AdmZip = require("adm-zip");
var fs = require("fs");
var http = require("http");
var os = require("os");
var path = require("path");
var Q = require("q");
var querystring = require("querystring");
var util = require("util");
var ServerMock = require("./utils/serverMock");
var Settings = require("../cli/utils/settings");
var TacoUtility = require("taco-utils");
var TacoTestUtils = require("taco-tests-utils");
var BuildInfo = TacoUtility.BuildInfo;
var utils = TacoUtility.UtilHelper;
var CommandHelper = require("./utils/commandHelper");
var MockCordova = TacoTestUtils.MockCordova;
var build = CommandHelper.getCommand("build");
var create = CommandHelper.getCommand("create");
/**
* Build/Run telemetry test plan
*
* Concerns to test:
* options: local, remote, clean, debug, release, device, emulator, target
* platforms: android, ios, windows, wp8
* + multiple platforms in a single command
* + no platforms specified in the command line, so we choose what to build
* + specifying contradictory options in the command line (e.g.: --debug --release) --> The command fails, so we don't need to test this
* remote build: project"s size, gziped project"s size, changed files" count, was incremental build, secure HTTPs server?
* Unexpected behavior: --uknown_option and unknown_platform
* Run specific options: nobuild, debuginfo
*
* Test cases:
* 1. android local clean release emulator
* 2. ios remote debug target secure_server incremental --> We are not testing secure_server currently
* 3. android ios unsecure_server not_incremental
* 4. no command line platforms, implicit windows wp8 device
* 5. --uknown_option unknown_platform
* 6. nobuild debuginfo (Only for Run)
*
* TODO: We currently aren't testing a secure server, because we need to find a good way of either
* installing the client certificate in both Windows and Mac, or mocking the certificate path
*/
var BuildAndRunTelemetryTests;
(function (BuildAndRunTelemetryTests) {
(function (Command) {
Command[Command["Build"] = 0] = "Build";
Command[Command["Run"] = 1] = "Run";
Command[Command["Emulate"] = 2] = "Emulate";
})(BuildAndRunTelemetryTests.Command || (BuildAndRunTelemetryTests.Command = {}));
var Command = BuildAndRunTelemetryTests.Command;
function createBuildAndRunTelemetryTests(runCommand, command) {
var tacoHome = path.join(os.tmpdir(), "taco-cli", commandSwitch("build", "run", "emulate"));
var projectPath = path.join(tacoHome, "example");
var testIosHttpServer;
var testAndroidHttpServer;
var iosPort = 3001;
var androidPort = 3002;
var cordova = MockCordova.MockCordova510.getDefault();
var vcordova = "4.0.0";
var buildNumber = 12341;
var isNotEmulate = command !== Command.Emulate;
var customLoader = {
lazyRequire: function (packageName, packageId, logLevel) {
return Q(cordova);
},
lazyRun: function (packageName, packageId, commandName) { return Q("cordova"); }
};
before(function () {
testIosHttpServer = http.createServer();
testIosHttpServer.listen(iosPort);
testAndroidHttpServer = http.createServer();
testAndroidHttpServer.listen(androidPort);
});
after(function () {
testIosHttpServer.close();
testAndroidHttpServer.close();
});
// We mock cordova build
cordova.raw.build = function (options) {
return Q({});
};
// We mock cordova run
cordova.raw.run = function (options) {
return Q({});
};
// We mock cordova emulate
cordova.raw.emulate = function (options) {
return Q({});
};
function generateCompleteBuildSequence(platform, port, isIncrementalTest) {
var configuration = "debug";
// Mock out the server on the other side
var queryOptions = {
command: "build",
vcordova: vcordova,
vcli: require(path.join(__dirname, "..", "package.json")).version,
cfg: configuration,
platform: platform
};
var zip = new AdmZip();
zip.addFile("test.txt", new Buffer("test file"), "comment");
var zippedAppBuffer = zip.toBuffer();
var nonIncrementalBuildStart = [{
expectedUrl: "/cordova/build/tasks?" + querystring.stringify(queryOptions),
head: {
"Content-Type": "application/json",
"Content-Location": "http://localhost:" + port + "/cordova/build/tasks/" + buildNumber
},
statusCode: 202,
response: JSON.stringify(new BuildInfo({ status: BuildInfo.UPLOADING, buildNumber: buildNumber, buildLang: "en" })),
waitForPayload: true
}];
queryOptions["buildNumber"] = "" + buildNumber;
var incrementalBuildStart = [{
expectedUrl: "/cordova/build/" + buildNumber,
head: { "Content-Type": "application/json" },
statusCode: 200,
response: JSON.stringify(new BuildInfo({ status: BuildInfo.COMPLETE, buildNumber: buildNumber, buildLang: "en" })),
waitForPayload: false
},
{
expectedUrl: "/cordova/build/tasks?" + querystring.stringify(queryOptions),
head: {
"Content-Type": "application/json",
"Content-Location": "http://localhost:" + port + "/cordova/build/tasks/" + buildNumber
},
statusCode: 202,
response: JSON.stringify(new BuildInfo({ status: BuildInfo.UPLOADING, buildNumber: buildNumber, buildLang: "en" })),
waitForPayload: true
}];
var remainingBuildSequence = [{
expectedUrl: "/cordova/build/tasks/" + buildNumber,
head: { "Content-Type": "application/json" },
statusCode: 200,
response: JSON.stringify(new BuildInfo({ status: BuildInfo.UPLOADED, buildNumber: buildNumber, buildLang: "en" })),
waitForPayload: false
},
{
expectedUrl: "/cordova/build/tasks/" + buildNumber + "/log?offset=0",
head: { "Content-Type": "application/json" },
statusCode: 200,
response: "1",
waitForPayload: false
},
{
expectedUrl: "/cordova/build/tasks",
head: {
"Content-Type": "application/json"
},
statusCode: 200,
response: JSON.stringify({ queued: 0, queuedBuilds: [] }),
waitForPayload: false
},
{
expectedUrl: "/cordova/build/tasks/" + buildNumber,
head: { "Content-Type": "application/json" },
statusCode: 200,
response: JSON.stringify(new BuildInfo({ status: BuildInfo.COMPLETE, buildNumber: buildNumber, buildLang: "en" })),
waitForPayload: false
},
{
expectedUrl: "/cordova/build/tasks/" + buildNumber + "/log?offset=1",
head: { "Content-Type": "application/json" },
statusCode: 200,
response: "2",
waitForPayload: false
},
{
expectedUrl: util.format("/cordova/files/%d/cordovaApp/plugins/%s.json", buildNumber, platform),
head: { "Content-Type": "application/json" },
statusCode: 200,
response: JSON.stringify({}),
waitForPayload: false
}
];
var buildSequence = (isIncrementalTest ? incrementalBuildStart : nonIncrementalBuildStart).concat(remainingBuildSequence);
if (command !== Command.Build) {
var target = isIncrementalTest ? "ipad 2" : "";
var runSequence = [{
expectedUrl: "/cordova/build/" + buildNumber + "/emulate?" + querystring.stringify({ target: target }),
head: { "Content-Type": "application/json" },
statusCode: 200,
response: JSON.stringify(new BuildInfo({ status: BuildInfo.EMULATED, buildNumber: buildNumber })),
waitForPayload: false
}];
buildSequence = buildSequence.concat(runSequence);
}
return buildSequence;
}
function configureRemoteServer(done, isIncrementalTest) {
var iosSequence = generateCompleteBuildSequence("ios", iosPort, isIncrementalTest);
var androidSequence = generateCompleteBuildSequence("android", androidPort, isIncrementalTest);
var iosServerFunction = ServerMock.generateServerFunction(done, iosSequence);
var androidServerFunction = ServerMock.generateServerFunction(done, androidSequence);
testIosHttpServer.on("request", iosServerFunction);
if (!isIncrementalTest) {
testAndroidHttpServer.on("request", androidServerFunction);
}
var platforms = { ios: { host: "localhost", port: iosPort, secure: false, mountPoint: "cordova" } };
if (!isIncrementalTest) {
platforms["android"] = { host: "localhost", port: androidPort, secure: false, mountPoint: "cordova" };
}
return Settings.saveSettings({ remotePlatforms: platforms });
}
var expectedGzipedSizeAbsoluteError = 60; /* This is how much the gzip size changes because of the different
compression rate of different file modification dates, etc... */
// We use this function to validate that the gzip size is near the expected ratio (non-deterministic changes in dates or other
// numbers might change the compression ratio, so it's difficult to predict the exact size), and then replace the number with
// the expected size, so we can compare it by eql with the expected full telemetry properties
function validateGzipedSize(telemetryProperties, platform, expectedGzippedSize) {
var keyName = "remotebuild." + platform + ".gzipedProjectSizeInBytes";
if (expectedGzippedSize !== -1) {
var value = telemetryProperties[keyName].value;
value.should.be.above(expectedGzipedSizeAbsoluteError - expectedGzippedSize);
value.should.be.below(expectedGzipedSizeAbsoluteError + expectedGzippedSize);
telemetryProperties[keyName].value = String(expectedGzippedSize);
}
else {
(typeof telemetryProperties[keyName] === "undefined").should.be.equal(true);
}
}
function telemetryShouldEqual(telemetryProperties, expected, iosExpectedGzipedSize, androidGzipSize) {
if (iosExpectedGzipedSize === void 0) { iosExpectedGzipedSize = -1; }
if (androidGzipSize === void 0) { androidGzipSize = -1; }
(typeof telemetryProperties === "undefined").should.be.equal(false);
validateGzipedSize(telemetryProperties, "ios", iosExpectedGzipedSize);
validateGzipedSize(telemetryProperties, "android", androidGzipSize);
telemetryProperties.should.containEql(expected); // We are comparing the objects, after overriding the sizes with the expected values
}
beforeEach(function (done) {
// Warning: After this line, all cordova CLI commands will have to be mocked
TacoUtility.TacoPackageLoader.mockForTests = customLoader;
Settings.saveSettings({ remotePlatforms: {} })
.done(function () { return done(); }, done);
});
afterEach(function () {
TacoUtility.TacoPackageLoader.mockForTests = null;
});
function commandSwitch(buildResult, runResult, emulateResult) {
switch (command) {
case Command.Build:
return buildResult;
case Command.Run:
return runResult;
case Command.Emulate:
return emulateResult;
default:
throw new Error("Unknown command");
}
}
it("1. android local clean release emulator", function (done) {
var args = ["--local", "--release", "android"];
var expected = {
"options.local": { isPii: false, value: "true" },
"options.release": { isPii: false, value: "true" },
"platforms.requestedViaCommandLine.local1": { isPii: false, value: "android" }
};
if ((command === Command.Build)) {
args.unshift("--clean"); // Only build supports clean
expected["options.clean"] = { isPii: false, value: "true" };
}
else if (command !== Command.Emulate) {
args.unshift("--emulator"); // Emulator doesn't support emulator
expected["options.emulator"] = { isPii: false, value: "true" };
}
if (command !== Command.Run) {
expected["platforms.actuallyBuilt.local1"] = { isPii: false, value: "android" };
}
runCommand(args).then(function (telemetryProperties) {
telemetryShouldEqual(telemetryProperties, expected);
}).done(function () { return done(); }, done);
});
function mockProjectWithIncrementalBuild() {
// We write an empty changes file, and a build info file so we'll get an incremental build
var changeTimeFileDirectory = path.join(projectPath, "remote", "ios", "debug");
utils.createDirectoryIfNecessary(changeTimeFileDirectory);
var changeTimeFile = path.join(changeTimeFileDirectory, "lastChangeTime.json");
var buildInfoFile = path.join(changeTimeFileDirectory, "buildInfo.json");
fs.writeFileSync(changeTimeFile, "{}");
fs.writeFileSync(buildInfoFile, "{\"buildNumber\": " + buildNumber + "}");
}
it("2. TestCordovaExempt ios remote debug target non_secure_server incremental", function (done) {
var args = ["--remote", "--debug", "--target=ipad 2", "ios"];
var expected = {
"options.remote": { isPii: false, value: "true" },
"options.debug": { isPii: false, value: "true" },
"options.target": { isPii: false, value: "ipad 2" },
"platforms.actuallyBuilt.remote1": { isPii: false, value: "ios" },
"platforms.requestedViaCommandLine.remote1": { isPii: false, value: "ios" },
"platforms.remote.ios.is_secure": { isPii: false, value: "false" },
"remoteBuild.ios.filesChangedCount": { isPii: false, value: 8 },
"remoteBuild.ios.wasIncremental": { isPii: false, value: "true" },
"remotebuild.ios.gzipedProjectSizeInBytes": { isPii: false, value: "28382" },
"remotebuild.ios.projectSizeInBytes": { isPii: false, value: "48128" }
};
mockProjectWithIncrementalBuild();
configureRemoteServer(done, /* Incremental test*/ true)
.then(function () { return runCommand(args); })
.finally(function () {
testIosHttpServer.removeAllListeners("request");
testAndroidHttpServer.removeAllListeners("request");
})
.then(function (telemetryProperties) {
telemetryShouldEqual(telemetryProperties, expected, 28382);
}).done(function () { return done(); }, done);
});
it("3. TestCordovaExempt android ios unsecure_server not_incremental", function (done) {
var args = ["android", "ios"];
var expected = {
"platforms.actuallyBuilt.remote1": { isPii: false, value: "android" },
"platforms.actuallyBuilt.remote2": { isPii: false, value: "ios" },
"platforms.requestedViaCommandLine.remote1": { isPii: false, value: "android" },
"platforms.requestedViaCommandLine.remote2": { isPii: false, value: "ios" },
"platforms.remote.android.is_secure": { isPii: false, value: "false" },
"platforms.remote.ios.is_secure": { isPii: false, value: "false" },
"remoteBuild.android.filesChangedCount": { isPii: false, value: 8 },
"remoteBuild.android.wasIncremental": { isPii: false, value: "false" },
"remotebuild.android.gzipedProjectSizeInBytes": { isPii: false, value: "28379" },
"remotebuild.android.projectSizeInBytes": { isPii: false, value: "48128" },
"remoteBuild.ios.filesChangedCount": { isPii: false, value: 8 },
"remoteBuild.ios.wasIncremental": { isPii: false, value: "false" },
"remotebuild.ios.gzipedProjectSizeInBytes": { isPii: false, value: "28379" },
"remotebuild.ios.projectSizeInBytes": { isPii: false, value: "48128" }
};
configureRemoteServer(done, /* Not incremental test*/ false)
.then(function () { return runCommand(args); })
.finally(function () {
testIosHttpServer.removeAllListeners("request");
testAndroidHttpServer.removeAllListeners("request");
})
.then(function (telemetryProperties) { return telemetryShouldEqual(telemetryProperties, expected, 28379, 28379); })
.done(function () { return done(); }, done);
});
it("4. no command line platforms, implicit windows wp8 device", function (done) {
// taco platform add windows wp8: We mock adding the platform
utils.createDirectoryIfNecessary(path.join(projectPath, "platforms", "windows"));
utils.createDirectoryIfNecessary(path.join(projectPath, "platforms", "wp8"));
var expected = {
"platforms.actuallyBuilt.local1": { isPii: false, value: "windows" },
"platforms.actuallyBuilt.local2": { isPii: false, value: "wp8" }
};
var args = [];
if (isNotEmulate) {
args.unshift("--device");
expected["options.device"] = { isPii: false, value: "true" };
}
runCommand(args)
.then(function (telemetryProperties) { return telemetryShouldEqual(telemetryProperties, expected); })
.then(function () { return done(); }, done);
});
it("5. --uknown_option unknown_platform", function (done) {
var args = ["--uknown_option=unknown_value", "unknown_platform"];
var expected = {
"platforms.requestedViaCommandLine.local1": { isPii: true, value: "unknown_platform" },
"platforms.actuallyBuilt.local1": { isPii: true, value: "unknown_platform" },
"unknownOption1.name": { isPii: true, value: "uknown_option" },
"unknownOption1.value": { isPii: true, value: "unknown_value" }
};
runCommand(args).then(function (telemetryProperties) {
telemetryShouldEqual(telemetryProperties, expected);
}).done(function () { return done(); }, done);
});
if ((command !== Command.Build)) {
it("6. nobuild debuginfo", function (done) {
utils.createDirectoryIfNecessary(path.join(projectPath, "platforms", "android"));
var args = ["--nobuild", "--debuginfo", "android"];
var expected = {
"options.nobuild": { isPii: false, value: "true" },
"options.debuginfo": { isPii: false, value: "true" },
"platforms.actuallyBuilt.local1": { isPii: false, value: "android" },
"platforms.requestedViaCommandLine.local1": { isPii: false, value: "android" }
};
runCommand(args)
.then(function (telemetryProperties) { return telemetryShouldEqual(telemetryProperties, expected); })
.then(function () { return done(); }, done);
});
}
}
BuildAndRunTelemetryTests.createBuildAndRunTelemetryTests = createBuildAndRunTelemetryTests;
})(BuildAndRunTelemetryTests || (BuildAndRunTelemetryTests = {}));
module.exports = BuildAndRunTelemetryTests;
//# sourceMappingURL=buildAndRunTelemetry.js.map