Skip to content
This repository has been archived by the owner on Oct 25, 2023. It is now read-only.

Commit

Permalink
feat: link global package if local not found
Browse files Browse the repository at this point in the history
  • Loading branch information
imurchie committed Aug 26, 2019
1 parent 27d9b32 commit 5a83673
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 28 deletions.
27 changes: 19 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
os: linux
dist: xenial
language: node_js
os:
- linux
node_js:
- "10"
- "12"
Expand All @@ -10,23 +11,33 @@ addons:
- llvm-toolchain-r-test
packages:
- clang-5.0
cache:
directories:
- "$(npm root -g)"
env:
global:
- CXX=clang++-5.0
- CC=clang-5.0
- _FORCE_LOGS=1
install:
# on node 12 opencv4nodejs and mjpeg-consumer cannot be installed
# also handle possible travis caching
- if [[ `node --version` != v12* ]]; then
printf "while [ true ]; do\nsleep 30\necho 'Building OpenCV'\ndone" > ping.sh;
bash ping.sh &
echo $! > ping.pid;
npm i opencv4nodejs > /dev/null 2>&1;
kill `cat ping.pid`;
if [[ $(npm ls --depth 1 -g opencv4nodejs) =~ "── opencv4nodejs@" ]]; then
printf "while [ true ]; do\nsleep 30\necho 'Building OpenCV'\ndone" > ping.sh;
bash ping.sh &
echo $! > ping.pid;
npm install -g opencv4nodejs > /dev/null 2>&1;
kill `cat ping.pid`;
fi

npm install --no-save mjpeg-consumer;
if [[ $(npm ls --depth 1 -g mjpeg-consumer) =~ "── mjpeg-consumer@" ]]; then
npm install -g mjpeg-consumer;
fi
fi
- npm install
script:
- npm run test && npm run e2e-test
- npm run test
- npm run e2e-test
after_success:
- npm run coverage
5 changes: 3 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as process from './lib/process';
import * as zip from './lib/zip';
import * as imageUtil from './lib/image-util';
import * as mjpeg from './lib/mjpeg';
import * as node from './lib/node';


const { fs } = fsIndex;
Expand All @@ -18,9 +19,9 @@ const { mkdirp } = mkdirpIndex;

export {
tempDir, system, util, fs, cancellableDelay, plist, mkdirp, logger, process,
zip, imageUtil, net, mjpeg
zip, imageUtil, net, mjpeg, node,
};
export default {
tempDir, system, util, fs, cancellableDelay, plist, mkdirp, logger, process,
zip, imageUtil, net, mjpeg
zip, imageUtil, net, mjpeg, node,
};
29 changes: 19 additions & 10 deletions lib/image-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { Buffer } from 'buffer';
import { PNG } from 'pngjs';
import B from 'bluebird';
import { hasValue } from './util';
import log from './logger';
import { requirePackage } from './node';


const { MIME_JPEG, MIME_PNG, MIME_BMP } = Jimp;
let cv = null;
Expand Down Expand Up @@ -90,15 +93,21 @@ async function getJimpImage (data) {
/**
* @throws {Error} If opencv4nodejs module is not installed or cannot be loaded
*/
function initOpenCV () {
if (!cv) {
try {
cv = require('opencv4nodejs');
} catch (ign) {}
async function initOpenCV () {
if (cv) {
return;
}

log.debug(`Initializing opencv`);
try {
cv = await requirePackage('opencv4nodejs');
} catch (err) {
log.warn(`Unable to load 'opencv4nodejs': ${err.message}`);
}

if (!cv) {
throw new Error('opencv4nodejs module is required to use OpenCV features. ' +
'Please install it first (npm i -g opencv4nodejs) and restart Appium. ' +
throw new Error(`'opencv4nodejs' module is required to use OpenCV features. ` +
`Please install it first ('npm i -g opencv4nodejs') and restart Appium. ` +
'Read https://github.com/justadudewhohacks/opencv4nodejs#how-to-install for more details on this topic.');
}
}
Expand Down Expand Up @@ -235,7 +244,7 @@ function highlightRegion (mat, region) {
* @throws {Error} If `detectorName` value is unknown.
*/
async function getImagesMatches (img1Data, img2Data, options = {}) {
initOpenCV();
await initOpenCV();

const {detectorName = 'ORB', visualize = false,
goodMatchesFactor, matchFunc = 'BruteForce'} = options;
Expand Down Expand Up @@ -340,7 +349,7 @@ async function getImagesMatches (img1Data, img2Data, options = {}) {
* @throws {Error} If the given images have different resolution.
*/
async function getImagesSimilarity (img1Data, img2Data, options = {}) {
initOpenCV();
await initOpenCV();

const {visualize = false} = options;
let [template, reference] = await B.all([
Expand Down Expand Up @@ -422,7 +431,7 @@ async function getImagesSimilarity (img1Data, img2Data, options = {}) {
* @throws {Error} If no occurences of the partial image can be found in the full image
*/
async function getImageOccurrence (fullImgData, partialImgData, options = {}) {
initOpenCV();
await initOpenCV();

const {visualize = false, threshold = DEFAULT_MATCH_THRESHOLD} = options;
const [fullImg, partialImg] = await B.all([
Expand Down
1 change: 1 addition & 0 deletions lib/logging.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function getLogger (prefix = null) {
enumerable: true,
configurable: true
});

// This lambda function is necessary to workaround unexpected memory leaks
// caused by NodeJS behavior described in https://bugs.chromium.org/p/v8/issues/detail?id=2869
const unleakIfString = (x) => _.isString(x) ? ` ${x}`.substr(1) : x;
Expand Down
9 changes: 5 additions & 4 deletions lib/mjpeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import B from 'bluebird';
import { getJimpImage, MIME_PNG } from './image-util';
import mJpegServer from 'mjpeg-server';
import { Writable } from 'stream';
import { requirePackage } from './node';


// lazy load this, as it might not be available
Expand All @@ -14,10 +15,10 @@ let MJpegConsumer = null;
/**
* @throws {Error} If `mjpeg-consumer` module is not installed or cannot be loaded
*/
function initMJpegConsumer () {
async function initMJpegConsumer () {
if (!MJpegConsumer) {
try {
MJpegConsumer = require('mjpeg-consumer');
MJpegConsumer = await requirePackage('mjpeg-consumer');
} catch (ign) {}
}
if (!MJpegConsumer) {
Expand All @@ -44,8 +45,6 @@ class MJpegStream extends Writable {
constructor (mJpegUrl, errorHandler = _.noop, options = {}) {
super(options);

initMJpegConsumer();

this.errorHandler = errorHandler;
this.url = mJpegUrl;
this.clear();
Expand Down Expand Up @@ -107,6 +106,8 @@ class MJpegStream extends Writable {
// ensure we're not started already
this.stop();

await initMJpegConsumer();

this.consumer = new MJpegConsumer();

// use the deferred pattern so we can wait for the start of the stream
Expand Down
49 changes: 49 additions & 0 deletions lib/node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { isWindows } from './system';
import log from './logger';
import { exec } from 'teen_process';


/**
* Internal utility to link global package to local context
*
* @returns {string} - name of the package to link
* @throws {Error} If the command fails
*/
async function linkGlobalPackage (packageName) {
try {
log.debug(`Linking package '${packageName}'`);
const cmd = isWindows() ? 'npm.cmd' : 'npm';
await exec(cmd, ['link', packageName], {timeout: 20000});
} catch (err) {
const msg = `Unable to link package '${packageName}': ${err.message}`;
log.debug(msg);
throw new Error(msg);
}
}

/**
* Utility function to extend node functionality, allowing us to require
* modules that are installed globally. If the package cannot be required,
* this will attempt to link the package and then re-require it
*
* @param {string} packageName - the name of the package to be required
* @returns {object} - the package object
* @throws {Error} If the package is not found locally or globally
*/
async function requirePackage (packageName) {
try {
log.debug(`Loading local package '${packageName}'`);
return require(packageName);
} catch (err) {
log.debug(`Failed to load package '${packageName}': ${err.message}`);
await linkGlobalPackage(packageName);
}
try {
log.debug(`Retrying load of local package '${packageName}'`);
return require(packageName);
} catch (err) {
log.errorAndThrow(`Unable to load package '${packageName}': ${err.message}`);
}
}

export { requirePackage };
5 changes: 5 additions & 0 deletions test/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"func-names": 0
}
}
4 changes: 2 additions & 2 deletions test/image-util-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('image-util', function () {

describe('OpenCV helpers', function () {
// OpenCV needs several seconds for initialization
this.timeout(20000);
this.timeout(120000);

let imgFixture = null;
let fullImage = null;
Expand All @@ -73,7 +73,7 @@ describe('image-util', function () {
});

describe('getImagesMatches', function () {
it('should calculate the number of matches between two images', async function () {
it.only('should calculate the number of matches between two images', async function () { // eslint-disable-line
for (const detectorName of ['AKAZE', 'ORB']) {
const {count, totalCount} = await getImagesMatches(fullImage, fullImage, {detectorName});
count.should.be.above(0);
Expand Down
4 changes: 2 additions & 2 deletions test/mjpeg-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ const MJPEG_SERVER_URL = `http://localhost:${MJPEG_SERVER_PORT}`;
describe('MJpeg Stream (e2e)', function () {
let mJpegServer, stream;

before(function () {
before(async function () {
// TODO: remove when buffertools can handle v12
if (process.version.startsWith('v12')) {
return this.skip();
}

mJpegServer = initMJpegServer(MJPEG_SERVER_PORT);
mJpegServer = await initMJpegServer(MJPEG_SERVER_PORT);
});

after(function () {
Expand Down
21 changes: 21 additions & 0 deletions test/node-e2e-specs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { node } from '..';


chai.should();
chai.use(chaiAsPromised);

describe('node utilities', function () {
describe('requirePackage', function () {
it('should be able to require a local package', async function () {
await node.requirePackage('chai').should.not.be.rejected;
});
it('should be able to require a global package', async function () {
await node.requirePackage('npm').should.not.be.rejected;
});
it('should fail to find uninstalled package', async function () {
await node.requirePackage('appium-foo-driver').should.eventually.be.rejectedWith(/Unable to load package/);
});
});
});

0 comments on commit 5a83673

Please sign in to comment.