Skip to content

Commit

Permalink
Merge 756356b into 735785d
Browse files Browse the repository at this point in the history
  • Loading branch information
FRSgit committed Oct 20, 2019
2 parents 735785d + 756356b commit d9f080d
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 131 deletions.
26 changes: 23 additions & 3 deletions .travis.yml
@@ -1,4 +1,24 @@
dist: trusty
sudo: required
language: node_js
node_js:
- "node"
- "lts/*"
cache: yarn
matrix:
include:
- name: "Standard linting"
script: yarn standard
node_js: "lts/*"
- name: "Unit tests & coverage"
script: yarn test
node_js:
- "node"
- "lts/*"
- name: "Benchmark test - glob"
script: yarn test:benchmark:glob
node_js:
- "node"
- "lts/*"
- name: "Benchmark test - string"
script: yarn test:benchmark:string
node_js:
- "node"
- "lts/*"
20 changes: 10 additions & 10 deletions README.md
Expand Up @@ -7,7 +7,7 @@

# FRS-replace

CLI & Node wrapper around [javascript replace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace) which allows on-the-fly replacing (with or without changing input files), [globbing](https://en.wikipedia.org/wiki/Glob_(programming)), [piping](https://en.wikipedia.org/wiki/Pipeline_(Unix)) and many more!
The fastest ([see benchmarks](#benchmarks)) CLI & Node wrapper around [javascript replace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace) which allows on-the-fly replacing (with or without changing input files), [globbing](https://en.wikipedia.org/wiki/Glob_(programming)), [piping](https://en.wikipedia.org/wiki/Pipeline_(Unix)) and many more!

* [Installation](#installation)
* [Node API usage](#node-api-usage)
Expand Down Expand Up @@ -230,22 +230,22 @@ FRS-replace a b --content abcd -o foo_replaced.js
FRS-replace a b -i foo.js | <next-command>
```

## Benchmarks
## Benchmarks (Node v10.11.0)
#### input as glob pattern [1000 iterations x 100 repetitions]
| Library (best&nbsp;bolded) | Execution time [s] | Difference percentage (comparing&nbsp;to&nbsp;best&nbsp;time) |
| --- | --- | --- |
| **FRS-replace async** | 0.36640944 | 0.0000% |
| FRS-replace sync | 0.39553770 | 7.9496% |
| replace-in-file | 1.78587186 | 387.3979% |
| replace async | *N/A* | *N/A* |
| replace sync | 0.44655926 | 21.8744% |
| **FRS-replace async** | 0.15219495 | 0.0000% |
| FRS-replace sync | 0.62943626 | 313.5724% |
| replace-in-file | 1.80808213 | 1088.0040% |
| replace async | 0.21353394 | 40.3029% |
| replace sync | 1.53610279 | 909.2995% |
| replace-string | *N/A* | *N/A* |
#### input & replacement as strings [1000 iterations x 100 repetitions]
| Library (best&nbsp;bolded) | Execution time [s] | Difference percentage (comparing&nbsp;to&nbsp;best&nbsp;time) |
| --- | --- | --- |
| FRS-replace async | 0.01015828 | 59.0095% |
| FRS-replace sync | 0.00657347 | 2.8957% |
| FRS-replace async | 0.04848095 | 194.2632% |
| **FRS-replace sync** | 0.01647537 | 0.0000% |
| replace-in-file | *N/A* | *N/A* |
| replace async | *N/A* | *N/A* |
| replace sync | *N/A* | *N/A* |
| **replace-string** | 0.00638847 | 0.0000% |
| replace-string | 0.01843529 | 11.8961% |
179 changes: 89 additions & 90 deletions benchmark/multiple-file-replace.benchmark.test.js
Expand Up @@ -16,8 +16,8 @@ const content = `aąbcćdeęfg%hi
jklmn
oópqr,stuvwxyZ`
const tmpPrefixes = {
input: 'FRS-replace-replace-in',
output: 'FRS-replace-replace-out'
input: 'FRS-replace-replace-in-',
output: 'FRS-replace-replace-out-'
}
const defaults = {
inputReadOptions: 'utf8',
Expand All @@ -26,7 +26,33 @@ const defaults = {
}
const repetitionsNo = 100000
const iterationsNo = 1000
const testInput = {}
const inputFilesNo = 40
const testInput = {
FRSReplace: {
regex,
replacement
},

replace: {
regex,
replacement,
recursive: true,
silent: true
},

replaceAsync: {
regex,
replacement,
async: true,
recursive: true,
silent: true
},

replaceInFile: {
from: regex,
to: replacement
}
}
const testedLibraries = [
'FRS-replace async',
'FRS-replace sync',
Expand All @@ -36,11 +62,9 @@ const testedLibraries = [
'replace-string'
]

let dir, output, input

const readmeContent = fs.readFileSync('./README.md').toString()

let dir; let output; let inputs = []
let perfyResults = ''
let tmpFilesPromise

{
const dirObj = tmp.dirSync() // removing all files similar our tmp
Expand All @@ -53,90 +77,67 @@ let perfyResults = ''
].map(v => v + '*')
)
.forEach(fs.unlinkSync)
}

tap.beforeEach(async () => {
testInput.FRSReplace = {
regex,
replacement
}

testInput.replace = {
regex,
replacement
}

testInput.replaceAsync = {
regex,
replacement,
async: true
}

testInput.replaceInFile = {
from: regex,
to: replacement
const promises = []

for (let i = 0; i < inputFilesNo; ++i) {
promises.push(
tmp.file({ prefix: tmpPrefixes.input + i + '-', keep: true, dir })
.then(input => {
inputs.push(input)
return new Promise(resolve => {
fs.appendFile(input.path, content, { encoding: defaults.inputReadOptions }, resolve)
})
})
)
}

cleanInputs()

await tmp.file({ prefix: tmpPrefixes.input, keep: true, dir })
.then(
async f => {
input = f
return new Promise(
resolve => fs.appendFile(f.path, content, { encoding: defaults.inputReadOptions }, resolve)
)
})
})

const cleanInputs = (done) => {
input && input.cleanup()
input = undefined
done && done() // to be runned either by node-tap or manually
tmpFilesPromise = Promise.all(promises)
}

tap.afterEach((done) => {
tap.autoend(false)
tap.beforeEach(() => tmpFilesPromise)
tap.afterEach(done => {
fs.existsSync(output) && fs.unlinkSync(output)
cleanInputs()
done()
})

tap.test(`input as glob pattern [${iterationsNo} iterations x ${repetitionsNo / iterationsNo} repetitions]`, async ct => {
tap.teardown(() => {
inputs.forEach(input => input.cleanup)
inputs = []
const readmeContent = fs.readFileSync('./README.md').toString()

fs.writeFileSync('./README.md', readmeContent.replace(/(##\sBenchmarks \(Node )(?:.*?)(\)\s)[\s\S]*?(?:$|(?:\s##\s))/, `$1${process.version}$2${perfyResults}`))
})

tap.test(`input as glob pattern [${inputFilesNo} files x ${iterationsNo} iterations x ${repetitionsNo / iterationsNo} repetitions]`, async ct => {
const results = await multipleTests(ct, [
{
fn: () => FRSreplace.async(testInput.FRSReplace),
before: () => (testInput.FRSReplace.input = `${dir}\\${tmpPrefixes.input}*`)
before: () => (testInput.FRSReplace.input = `${dir}/${tmpPrefixes.input}*`)
},
{
fn: () => FRSreplace.sync(testInput.FRSReplace),
before: () => (testInput.FRSReplace.input = `${dir}\\${tmpPrefixes.input}*`)
before: () => (testInput.FRSReplace.input = `${dir}/${tmpPrefixes.input}*`)
},
{
fn: () => replaceInFile(testInput.replaceInFile),
before: () => (testInput.replaceInFile.files = dir + require('path').sep + tmpPrefixes.input + '*')
before: () => (testInput.replaceInFile.files = `${dir}/${tmpPrefixes.input}*`)
},
// {
// fn: () => replace(testInput.replaceAsync), before: () => {
// testInput.replaceAsync.paths = [dir.replace(/\\/g, '/')]
// testInput.replaceAsync.include = `${tmpPrefixes.input}*`
// }
// }, // COMMENTED OUT - waits for better FRS-replace async methods
undefined,
undefined, // IMPORTANT: test doesn't checks replace async, because it doesn't returns when (and if) file got replaced(https://github.com/harthur/replace/issues/25)
{
fn: () => replace(testInput.replace),
before: () => {
testInput.replace.paths = [dir.replace(/\\/g, '/')]
testInput.replace.include = `${tmpPrefixes.input}*`
}
},
undefined
])
const sortedResults = results.slice().sort(sortByNanoseconds)

ct.not(sortedResults[0].name.indexOf('FRS-replace'), -1, 'FRS-replace should be the fastest')
// results.map((result) => result.testCfg && ct.is(result.result, results[0].result, `${result.name} are results the same`))
const result = outputPerfy(ct, results, results.slice().sort(sortByNumberVariable('fullNanoseconds'))[0])
const sortedResults = result.results.slice().sort(sortByNumberVariable('avgTime'))

outputPerfy(ct, results, sortedResults[0])
ct.is((sortedResults[0].name.indexOf('FRS-replace sync') !== -1 || (sortedResults[1].name.indexOf('FRS-replace sync') !== -1 && sortedResults[1].avgPercentageDifference < 5)), true, 'FRS-replace sync should be the fastest or second, but at most with 5% difference to best')
ct.is(sortedResults[0].name.indexOf('FRS-replace async') !== -1 || sortedResults[1].name.indexOf('FRS-replace async') !== -1, true, 'FRS-replace async should be the fastest or second')

ct.end()
})
Expand All @@ -163,19 +164,14 @@ tap.test(`input & replacement as strings [${iterationsNo} iterations x ${repetit
{ fn: () => replaceString(content, regex.source, replacement) }
])

const result = outputPerfy(ct, results, results.slice().sort(sortByNanoseconds)[0])

const sortedResults = result.results.slice().sort(sortByNanoseconds)
const result = outputPerfy(ct, results, results.slice().sort(sortByNumberVariable('fullNanoseconds'))[0])
const sortedResults = result.results.slice().sort(sortByNumberVariable('avgTime'))

ct.is((sortedResults[0].name.indexOf('FRS-replace') !== -1 || (sortedResults[1].name.indexOf('FRS-replace') !== -1 && sortedResults[1].avgPercentageDifference < 10)), true, 'FRS-replace should be the fastest or second, but at most with 10% difference to best')

ct.end()
})

tap.teardown(() => {
fs.writeFileSync('./README.md', readmeContent.replace(/(##\sBenchmarks\s\s)[\s\S]*?(?:$|(?:\s##\s))/, '$1' + perfyResults))
})

function outputPerfy (t, testResults, best) {
best = best.fullNanoseconds

Expand Down Expand Up @@ -216,7 +212,7 @@ function outputPerfy (t, testResults, best) {
)

perfyResults +=
'#### ' + result.name + '\n' +
'\n### ' + result.name + '\n\n' +
'| Library (best&nbsp;bolded) | Execution time [s] | Difference percentage (comparing&nbsp;to&nbsp;best&nbsp;time) |\n' +
'| --- | --- | --- |\n' +
result.results.reduce(
Expand Down Expand Up @@ -248,13 +244,12 @@ async function multipleTests (t, testCfgs, n, iterations) {

const testCfgLen = testCfgs.length

for (let i = 0; i < n; ++i) {
for (let k = testCfgLen - 1; k >= 0; --k) {
const { v: testCfg, i: index } = testCfgs[k]
const prevResult = results[index]
const libName = testedLibraries[index]

await t.test(`${t.name} - ${libName} #${i}`, async ct => {
for (let k = testCfgLen - 1; k >= 0; --k) {
const { v: testCfg, i: index } = testCfgs[k]
const prevResult = results[index]
const libName = testedLibraries[index]
await t.test(`${t.name} - ${libName}`, async ct => {
for (let i = 0; i < n; ++i) {
testCfg.before && testCfg.before()
const result = await singleTest(libName, testCfg.fn, iterations)

Expand All @@ -268,10 +263,9 @@ async function multipleTests (t, testCfgs, n, iterations) {
}
}
}

ct.end()
})
}
}
ct.end()
})
}

testCfgs.forEach(({ i: index }) => {
Expand Down Expand Up @@ -303,14 +297,19 @@ async function singleTest (name, test, n) {
return result
}

function sortByNanoseconds (a, b) {
if (a.fullNanoseconds === undefined) {
return b.fullNanoseconds === undefined ? 0 : 1
}
function sortByNumberVariable (varName) {
return (a, b) => {
a = a[varName]
b = b[varName]

if (b.fullNanoseconds === undefined) {
return -1
}
if (a === undefined || a === null) {
return b === undefined || b === null ? 0 : 1
}

return a.fullNanoseconds - b.fullNanoseconds
if (b === undefined || b === null) {
return -1
}

return a - b
}
}
15 changes: 9 additions & 6 deletions package.json
Expand Up @@ -35,15 +35,18 @@
"javascript"
],
"scripts": {
"prerelease": "standard --fix && yarn test",
"prerelease": "yarn standard:fix && yarn test && yarn test:benchmark",
"release": "standard-version",
"postrelease": "git push --follow-tags origin master && yarn publish",
"pretest": "standard",
"test": "yarn test:benchmark && yarn test:unit --100",
"test": "yarn test:unit --100",
"posttest": "tap --coverage-report=html",
"pretest:unit": "standard --fix",
"pretest:unit": "yarn standard:fix",
"test:unit": "tap ./src/*.test.js ./bin/*.test.js -J",
"test:benchmark": "tap ./benchmark/*.benchmark.test.js --no-timeout"
"test:benchmark": "tap ./benchmark/*.benchmark.test.js --no-timeout --no-coverage",
"test:benchmark:glob": "yarn test:benchmark --grep=\"/input as glob pattern/\"",
"test:benchmark:string": "yarn test:benchmark --grep=\"/input & replacement as strings/\"",
"standard:fix": "standard --fix"
},
"devDependencies": {
"perfy": "^1.1.5",
Expand All @@ -58,7 +61,7 @@
"dependencies": {
"fast-glob": "^3.1.0",
"get-stdin": "^7.0.0",
"yargs": "^14.2.0",
"write": "^2.0.0"
"write": "^2.0.0",
"yargs": "^14.2.0"
}
}

0 comments on commit d9f080d

Please sign in to comment.