UNPKG

@ronomon/opened

Version:

Check if a file is open in another application on Windows, macOS and Linux.

270 lines (246 loc) 6.26 kB
var Node = { fs: require('fs'), path: require('path'), process: process }; var random = Math.random.bind(Math); var Queue = require('@ronomon/queue'); var namespace = 'Opened'; var Test = {}; Test.equal = function(value, expected, namespace, description) { value = JSON.stringify(value) + ''; expected = JSON.stringify(expected) + ''; if (value === expected) { Test.pass(namespace, description, expected); } else { Test.fail(namespace, description, value + ' !== ' + expected); } }; Test.fail = function(namespace, description, message) { console.log(''); throw 'FAIL: ' + Test.message(namespace, description, message); }; Test.message = function(namespace, description, message) { if ((namespace = namespace || '')) namespace += ': '; if ((description = description || '')) description += ': '; return namespace + description + (message || ''); }; Test.pass = function(namespace, description, message) { console.log('PASS: ' + Test.message(namespace, description, message)); }; var root = Node.path.resolve(module.filename, '..'); if (!root) throw new Error('root must not be empty'); var fixtures = Node.path.join(root, 'opened_fixtures'); var binding = require(Node.path.join(root, 'index.js')); var ALPHABET = ''; ALPHABET += 'abcdefghijklmnopqrstuvwxyz'; ALPHABET += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; ALPHABET += '0123456789'; ALPHABET += 'àáâäæãåāèéêëēėę'; if (Node.process.platform === 'win32') { ALPHABET += ' '; } else { ALPHABET += '\b\f\t\n\r '; } function close(fds) { fds.forEach( function(fd) { Node.fs.closeSync(fd); } ); } function createPath(path) { Node.fs.writeFileSync(path, ''); } function createPaths(paths) { paths.forEach( function(path) { createPath(path); } ); } function generatePath() { var chars = 1 + Math.floor(random() * 16); var string = ''; while (string.length < chars) { string += ALPHABET[Math.floor(random() * ALPHABET.length)]; if (string.trim() === string) continue; string += ALPHABET[Math.floor(random() * ALPHABET.length)]; if (string.trim() === string) continue; string += ALPHABET[Math.floor(random() * ALPHABET.length)]; string = string.trim(); // Avoid leading/trailing space on Windows. } if (random() < 0.5) { if (random() < 0.5) { string = string.normalize('NFC'); } else { string = string.normalize('NFD'); } } return Node.path.join(fixtures, string); } function generatePaths() { var paths = []; var keys = {}; if (random() < 0.01) return paths; var length = Math.floor(random() * 128); while (length--) { var path = generatePath(); var key = path.toUpperCase().normalize('NFC'); if (keys.hasOwnProperty(key)) continue; keys[key] = 1; paths.push(path); } return paths; } function openPath(path, expect, fds) { if (random() < 0.5) { expect[path] = false; } else { expect[path] = true; fds.push(Node.fs.openSync(path, 'r+')); } } function openPaths(paths, expect, fds) { paths.forEach( function(path) { openPath(path, expect, fds); } ); } function removeFixtures(callback) { function remove() { try { var names = Node.fs.readdirSync(fixtures); } catch (error) { if (error.code !== 'ENOENT') throw error; var names = []; } names.forEach( function(name) { Node.fs.unlinkSync(Node.path.join(fixtures, name)); } ); try { Node.fs.rmdirSync(fixtures); } catch (error) { if (error.code !== 'ENOENT') throw error; } } if (callback) { setTimeout( function() { remove(); callback(); }, // Wait for handles to be released by antivirus: Node.process.platform === 'win32' ? 2000 : 0 ); } else { remove(); } } function removePath(path) { try { Node.fs.unlinkSync(path, ''); } catch (error) { // Ignore } } function removePaths(paths) { paths.forEach( function(path) { removePath(path); } ); } removeFixtures(); try { Node.fs.mkdirSync(fixtures); } catch (error) { if (error.code !== 'EEXIST') throw error; } var count = 0; var elapsed = 0; var maxCount = 0; var maxBatch = 0; var queue = new Queue(1); queue.onData = function(test, end) { var paths = generatePaths(); var expect = {}; var fds = []; createPaths(paths); openPaths(paths, expect, fds); var enoent = false; if (random() < 0.2) { enoent = generatePath(); paths.push(enoent); } var now = Date.now(); binding.files(paths, function(error, actual) { var time = Date.now() - now; count += paths.length; elapsed += time; if (time > maxBatch) { maxCount = paths.length; maxBatch = time; } try { if (enoent) { Test.equal(error.code, 'ENOENT', namespace, 'error'); close(fds); removePaths(paths); return end(); } else { Test.equal(error === undefined, true, namespace, 'error undefined'); } if (error) throw error; for (var a in actual) { Test.equal( expect.hasOwnProperty(a), true, namespace, JSON.stringify(a) + ': name' ); Test.equal( actual[a], expect[a], namespace, JSON.stringify(a) + ': open' ); } var x = Object.keys(actual).sort().join(''); var y = Object.keys(expect).sort().join(''); if (y !== x) { Test.equal(x, y, namespace, 'names'); } } catch (error) { close(fds); removePaths(paths); return end(error); } close(fds); removePaths(paths); end(); } ); }; queue.onEnd = function(error) { removeFixtures( function() { if (error) throw error; var avg = (elapsed / count).toFixed(5); console.log( avg + 'ms per file, ' + maxBatch + 'ms per ' + maxCount + ' file batch.' ); console.log('================'); console.log('PASSED ALL TESTS'); console.log('================'); } ); }; for (var test = 0; test < 10; test++) { queue.push(test); } queue.end();