Skip to content

Commit

Permalink
Return more appropriate info as server status and speed it up (appium…
Browse files Browse the repository at this point in the history
…#11232)

* Improve /status response for the umbrella driver
  • Loading branch information
Mykola Mokhnach committed Aug 28, 2018
1 parent c503f08 commit 41cc517
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 39 deletions.
14 changes: 6 additions & 8 deletions lib/appium.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _ from 'lodash';
import log from './logger';
import { getAppiumConfig } from './config';
import { getBuildInfo, updateBuildInfo } from './config';
import { BaseDriver, errors, isSessionCommand } from 'appium-base-driver';
import { FakeDriver } from 'appium-fake-driver';
import { AndroidDriver } from 'appium-android-driver';
Expand Down Expand Up @@ -148,6 +148,8 @@ class AppiumDriver extends BaseDriver {
// it might be changed by other async calls at any time
// It is not recommended to access this property directly from the outside
this.pendingDrivers = {};

updateBuildInfo();
}

/**
Expand Down Expand Up @@ -201,13 +203,9 @@ class AppiumDriver extends BaseDriver {
}

async getStatus () {
const config = await getAppiumConfig();
const gitSha = config['git-sha'];
const status = {build: {version: config.version}};
if (!_.isEmpty(gitSha)) {
status.build.revision = gitSha;
}
return status;
return {
build: _.clone(await getBuildInfo()),
};
}

async getSessions () {
Expand Down
127 changes: 107 additions & 20 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import _ from 'lodash';
import path from 'path';
import { mkdirp, fs } from 'appium-support';
import { mkdirp, fs, system, util } from 'appium-support';
import request from 'request-promise';
import { exec } from 'teen_process';
import { rootDir } from './utils';
import logger from './logger';

const APPIUM_VER = require(path.resolve(rootDir, 'package.json')).version;
const GIT_META_ROOT = '.git';
const GIT_BINARY = `git${system.isWindows() ? '.exe' : ''}`;
const GITHUB_API = 'https://api.github.com/repos/appium/appium';
const BUILD_INFO = {
version: APPIUM_VER,
};

function getNodeVersion () {
// expect v<major>.<minor>.<patch>
Expand All @@ -14,24 +21,103 @@ function getNodeVersion () {
return [Number(version[1]), Number(version[2])];
}

async function getGitRev () {
let rev = null;
async function updateBuildInfo () {
const sha = await getGitRev(true);
if (!sha) {
return;
}
BUILD_INFO['git-sha'] = sha;
const built = await getGitTimestamp(sha, true);
if (!_.isEmpty(built)) {
BUILD_INFO.built = built;
}
}

async function getGitRev (useGitHubFallback = false) {
if (await fs.exists(path.resolve(rootDir, GIT_META_ROOT))) {
try {
const {stdout} = await exec(GIT_BINARY, ['rev-parse', 'HEAD'], {
cwd: rootDir
});
return stdout.trim();
} catch (err) {
logger.warn(`Cannot retrieve git revision for Appium version ${APPIUM_VER} from the local repository. ` +
`Original error: ${err.message}`);
}
}

if (!useGitHubFallback) {
return null;
}

try {
let {stdout} = await exec("git", ["rev-parse", "HEAD"], {rootDir});
rev = stdout.trim();
} catch (ign) {}
return rev;
const response = await request.get(`${GITHUB_API}/tags`, {
headers: {
'User-Agent': `Appium ${APPIUM_VER}`
}
});
const resBodyObj = util.safeJsonParse(response);
if (_.isArray(resBodyObj)) {
for (const {name, commit} of resBodyObj) {
if (name === `v${APPIUM_VER}` && commit && commit.sha) {
return commit.sha;
}
}
}
} catch (err) {
logger.warn(`Cannot retrieve git revision for Appium version ${APPIUM_VER} from GitHub. ` +
`Original error: ${err.message}`);
}
return null;
}

async function getAppiumConfig () {
let stat = await fs.stat(path.resolve(__dirname, '..'));
let built = stat.mtime.getTime();
let config = {
'git-sha': await getGitRev(),
built,
version: APPIUM_VER,
};
return config;
async function getGitTimestamp (commitSha, useGitHubFallback = false) {
if (await fs.exists(path.resolve(rootDir, GIT_META_ROOT))) {
try {
const {stdout} = await exec(GIT_BINARY, ['show', '-s', '--format=%ci', commitSha], {
cwd: rootDir
});
return stdout.trim();
} catch (err) {
logger.warn(`Cannot retrieve the timestamp for Appium git commit ${commitSha} from the local repository. ` +
`Original error: ${err.message}`);
}
}

if (!useGitHubFallback) {
return null;
}

try {
const response = await request.get(`${GITHUB_API}/commits/${commitSha}`, {
headers: {
'User-Agent': `Appium ${APPIUM_VER}`
}
});
const resBodyObj = util.safeJsonParse(response);
if (resBodyObj && resBodyObj.commit) {
if (resBodyObj.commit.committer && resBodyObj.commit.committer.date) {
return resBodyObj.commit.committer.date;
}
if (resBodyObj.commit.author && resBodyObj.commit.author.date) {
return resBodyObj.commit.author.date;
}
}
} catch (err) {
logger.warn(`Cannot retrieve the timestamp for Appium git commit ${commitSha} from GitHub. ` +
`Original error: ${err.message}`);
}
return null;
}

/**
* @returns Mutable object containing Appium build information. By default it
* only contains the Appium version, but is updated with the build timestamp
* and git commit hash asynchronously as soon as `updateBuildInfo` is called
* and succeeds.
*/
async function getBuildInfo () {
return BUILD_INFO;
}

function checkNodeOk () {
Expand All @@ -52,8 +138,8 @@ function warnNodeDeprecations () {
}

async function showConfig () {
let config = await getAppiumConfig();
console.log(JSON.stringify(config)); // eslint-disable-line no-console
await updateBuildInfo();
console.log(JSON.stringify(await getBuildInfo())); // eslint-disable-line no-console
}

function getNonDefaultArgs (parser, args) {
Expand Down Expand Up @@ -142,6 +228,7 @@ async function validateTmpDir (tmpDir) {
}
}

export { getAppiumConfig, validateServerArgs, checkNodeOk, showConfig,
export { getBuildInfo, validateServerArgs, checkNodeOk, showConfig,
warnNodeDeprecations, validateTmpDir, getNonDefaultArgs,
getDeprecatedArgs, getGitRev, checkValidPort, APPIUM_VER };
getDeprecatedArgs, getGitRev, checkValidPort, APPIUM_VER,
updateBuildInfo };
100 changes: 89 additions & 11 deletions test/config-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import _ from 'lodash';
import chai from 'chai';
import sinon from 'sinon';
import chaiAsPromised from 'chai-as-promised';
import { getGitRev, getAppiumConfig, checkNodeOk, warnNodeDeprecations,
import { getGitRev, getBuildInfo, checkNodeOk, warnNodeDeprecations,
getNonDefaultArgs, getDeprecatedArgs, validateServerArgs,
validateTmpDir, showConfig, checkValidPort } from '../lib/config';
validateTmpDir, showConfig, checkValidPort, updateBuildInfo,
APPIUM_VER } from '../lib/config';
import getParser from '../lib/parser';
import logger from '../lib/logger';

import { fs } from 'appium-support';
import request from 'request-promise';

let should = chai.should();
chai.use(chaiAsPromised);
Expand All @@ -26,21 +28,97 @@ describe('Config', function () {
});

describe('Appium config', function () {
describe('getAppiumConfig', function () {
it('should get a configuration object', async function () {
let config = await getAppiumConfig();
config.should.be.an('object');
should.exist(config['git-sha']);
should.exist(config.built);
should.exist(config.version);
describe('getBuildInfo', function () {
async function verifyBuildInfoUpdate (useLocalGit) {
const buildInfo = await getBuildInfo();
mockFs.expects('exists').atLeast(1).returns(useLocalGit);
buildInfo['git-sha'] = undefined;
buildInfo.built = undefined;
await updateBuildInfo();
buildInfo.should.be.an('object');
should.exist(buildInfo['git-sha']);
should.exist(buildInfo.built);
should.exist(buildInfo.version);
}

let mockFs;
let getStub;
beforeEach(function () {
mockFs = sinon.mock(fs);
getStub = sinon.stub(request, 'get');
});
afterEach(function () {
getStub.restore();
mockFs.restore();
});

it('should get a configuration object if the local git metadata is present', async function () {
await verifyBuildInfoUpdate(true);
});
it('should get a configuration object if the local git metadata is not present', async function () {
getStub.onCall(0).returns([
{
"name": `v${APPIUM_VER}`,
"zipball_url": "https://api.github.com/repos/appium/appium/zipball/v1.9.0-beta.1",
"tarball_url": "https://api.github.com/repos/appium/appium/tarball/v1.9.0-beta.1",
"commit": {
"sha": "3c2752f9f9c56000705a4ae15b3ba68a5d2e644c",
"url": "https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c"
},
"node_id": "MDM6UmVmNzUzMDU3MDp2MS45LjAtYmV0YS4x"
},
{
"name": "v1.8.2-beta",
"zipball_url": "https://api.github.com/repos/appium/appium/zipball/v1.8.2-beta",
"tarball_url": "https://api.github.com/repos/appium/appium/tarball/v1.8.2-beta",
"commit": {
"sha": "5b98b9197e75aa85e7507d21d3126c1a63d1ce8f",
"url": "https://api.github.com/repos/appium/appium/commits/5b98b9197e75aa85e7507d21d3126c1a63d1ce8f"
},
"node_id": "MDM6UmVmNzUzMDU3MDp2MS44LjItYmV0YQ=="
}
]);
getStub.onCall(1).returns({
"sha": "3c2752f9f9c56000705a4ae15b3ba68a5d2e644c",
"node_id": "MDY6Q29tbWl0NzUzMDU3MDozYzI3NTJmOWY5YzU2MDAwNzA1YTRhZTE1YjNiYTY4YTVkMmU2NDRj",
"commit": {
"author": {
"name": "Isaac Murchie",
"email": "isaac@saucelabs.com",
"date": "2018-08-17T19:48:00Z"
},
"committer": {
"name": "Isaac Murchie",
"email": "isaac@saucelabs.com",
"date": "2018-08-17T19:48:00Z"
},
"message": "v1.9.0-beta.1",
"tree": {
"sha": "2c0974727470eba419ea0b9951c52f72f8036b18",
"url": "https://api.github.com/repos/appium/appium/git/trees/2c0974727470eba419ea0b9951c52f72f8036b18"
},
"url": "https://api.github.com/repos/appium/appium/git/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c",
"comment_count": 0,
"verification": {
"verified": false,
"reason": "unsigned",
"signature": null,
"payload": null
}
},
"url": "https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c",
"html_url": "https://github.com/appium/appium/commit/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c",
"comments_url": "https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c/comments",
});
await verifyBuildInfoUpdate(false);
});
});
describe('showConfig', function () {
before(function () {
sinon.spy(console, "log");
});
it('should log the config to console', async function () {
let config = await getAppiumConfig();
const config = await getBuildInfo();
await showConfig();
console.log.calledOnce.should.be.true; // eslint-disable-line no-console
console.log.getCall(0).args[0].should.contain(JSON.stringify(config)); // eslint-disable-line no-console
Expand Down

0 comments on commit 41cc517

Please sign in to comment.