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

Require from global if opencv4nodejs is not found locally #105

Merged
merged 5 commits into from
Aug 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
54 changes: 54 additions & 0 deletions lib/node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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 load package '${packageName}', linking failed: ${err.message}`;
log.debug(msg);
if (err.stderr) {
// log the stderr if there, but do not add to thrown error as it is
// _very_ verbose
log.debug(err.stderr);
}
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this only intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Trying to get Windows working on Travis.

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/);
});
});
});