Skip to content

Commit

Permalink
feat: add configurable output normalization
Browse files Browse the repository at this point in the history
  • Loading branch information
antongolub committed Sep 26, 2019
1 parent 328eedd commit 497cb64
Show file tree
Hide file tree
Showing 9 changed files with 409 additions and 153 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
},
"dependencies": {
"@qiwi/substrate": "^1.11.2",
"tslib": "^1.10.0"
"tslib": "^1.10.0",
"lodash": "^4.17.15"
},
"devDependencies": {
"@qiwi/semantic-release-gh-pages-plugin": "^1.10.6",
Expand All @@ -52,18 +53,19 @@
"@types/bluebird": "^3.5.27",
"@types/jest": "^24.0.18",
"@types/jest-json-schema": "^1.2.1",
"@types/lodash": "^4.14.139",
"bluebird": "^3.5.5",
"coveralls": "^3.0.6",
"dts-generator": "^3.0.0",
"eslint-plugin-typescript": "^0.14.0",
"esm": "^3.2.25",
"flowgen": "1.10.0",
"jest": "^24.9.0",
"lodash": "^4.17.15",
"microbundle": "^0.12.0-next.3",
"replace-in-file": "^4.1.3",
"rimraf": "^3.0.0",
"semantic-release": "^15.13.24",
"stdout-stderr": "^0.1.9",
"terser": "^4.3.1",
"ts-jest": "^24.1.0",
"tslint": "^5.20.0",
Expand Down
21 changes: 21 additions & 0 deletions src/main/ts/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {IStringHandler} from './interface'

export const relatify: IStringHandler = (out: string, dirname: string) => {
let index: number = 0
const getIndex = () => {
index = out.indexOf(dirname)
return index
}

while (getIndex() > -1) {
out = out.substr(0, index) + out.substr(index + dirname.length)
}

return out
}

export const fixEncoding: IStringHandler = (out: string) => out.replace(/\[\d*m?/g, '')

export const trim: IStringHandler = (str: string) => str.trim()

export const tabsToSpaces: IStringHandler = (str: string) => str.replace(/\t/gi, ' ')
119 changes: 118 additions & 1 deletion src/main/ts/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,118 @@
export default () => { /* foo */ }
// TODO use execa
import {exec} from 'child_process'
import {writeFile, readFile} from 'fs'
import {identity, get} from './util'
import {
trim,
tabsToSpaces,
relatify,
fixEncoding
} from './handler'

import {
IAnyMap,
IOpts,
ICmdOpts,
ISnapshot,
IStringHandler
} from './interface'

export const DEFAULT_OPTS = {
trim: true,
normalizeSpaces: true,
normalizePaths: true,
normalizeEncoding: true,
}

export const DEFAULT_CMD_OPTS: ICmdOpts = {cwd: __dirname}

export const processCmdOpts = (opts: ICmdOpts = {}): ICmdOpts => ({...DEFAULT_CMD_OPTS, opts})

export const getExecSnapshot = async (opts: IOpts): Promise<ISnapshot> => new Promise((resolve, reject) => {
const {cmd, cmdOpts} = opts
const _cmdOpts = processCmdOpts(cmdOpts)

exec(cmd, _cmdOpts, (err, stdout: string, stderr: string) => {
if (err) {
reject(err)
} else {
resolve({
stdout,
stderr,
opts
})
}
})
})

export const getSnapshot = async (filePath: string): Promise<ISnapshot> => new Promise((resolve, reject) => {
readFile(filePath, 'utf8', (err, result) => {
if (err) {
reject(err)
} else {
resolve(JSON.parse(result) as ISnapshot)
}
})
})

export const updateSnapshot = (filePath: string, snapshot: ISnapshot): Promise<any> => new Promise((resolve, reject) => {
const text = JSON.stringify(snapshot)

writeFile(filePath, text, (err) => {
if (err) {
reject(err)
} else {
resolve(text)
}
})
})

export const normalizeSnapshot = (snapshot: ISnapshot): ISnapshot => {
const opts: IOpts = snapshot.opts
const handlerMap: {[key: string]: IStringHandler} = {
normalizePaths: relatify,
normalizeEncoding: fixEncoding,
normalizeSpaces: tabsToSpaces,
trim: trim
}
const handlerOptsMap: IAnyMap = {
normalizePaths: [get(opts, 'cmdOpts.cwd')]
}

return Object.keys(opts).reduce((m: ISnapshot, k: string) => {
const v = opts[k]
const handler: IStringHandler = (v && handlerMap[k]) || identity
const handlerOpts: Array<any> = handlerOptsMap[k] || []

return applyHandler(handler, m, ...handlerOpts)
}, snapshot)
}

export const applyHandler = (handler: IStringHandler, snapshot: ISnapshot, ..._opts: any[]) => {
const {stdout, stderr, opts} = snapshot

return {
stderr: handler(stderr, ..._opts),
stdout: handler(stdout, ..._opts),
opts
}
}

export const process = async (opts: IOpts): Promise<boolean> => {
const _opts: IOpts = {...DEFAULT_OPTS, ...opts}
const {target, update} = _opts
const snapshot: ISnapshot = await getExecSnapshot(_opts).then(normalizeSnapshot)

if (update) {
await updateSnapshot(target, snapshot)
return true
}

const prevSnapshot = await getSnapshot(target)

return match(snapshot, prevSnapshot)
}

export const match = (prev: ISnapshot, next: ISnapshot): boolean =>
prev.stdout === next.stdout
&& prev.stderr === next.stderr
26 changes: 26 additions & 0 deletions src/main/ts/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export type IAnyMap = {
[key: string]: any
}

export type ICmdOpts = IAnyMap

export type IOpts = {
cmd: string
target: string
cmdOpts?: ICmdOpts,
update?: boolean,
normalizePaths?: boolean,
normalizeEncoding?: boolean,
trim?: boolean,
[key: string]: any
}



export type ISnapshot = {
stdout: string,
stderr: string,
opts: IOpts
}

export type IStringHandler = (v: string, ...args: any[]) => string
3 changes: 3 additions & 0 deletions src/main/ts/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const identity = <T>(v: T): T => v

export {get} from 'lodash'
1 change: 1 addition & 0 deletions src/test/snapshots/foo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"stderr":"","stdout":"/rules/some-rules.ts\n 2:7 error 'name' is assigned a value but never used no-unused-vars\n 3:7 error 'name' is already defined no-redeclare","opts":{"trim":true,"normalizeSpaces":true,"normalizePaths":true,"normalizeEncoding":true,"cmd":"echo \"\n\t/Users/antongolub/projects/stdstream-snapshot/src/test/ts/rules/some-rules.ts\n\t\t2:7 error 'name' is assigned a value but never used no-unused-vars\n\t\t3:7 error 'name' is already defined no-redeclare \n\n\n\"","target":"src/test/snapshots/foo.json","update":true,"cmdOpts":{"cwd":"/Users/antongolub/projects/stdstream-snapshot/src/test/ts"}}}
34 changes: 34 additions & 0 deletions src/test/ts/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
relatify,
// relatify,
// fixEncoding,
tabsToSpaces,
trim
} from '../../main/ts/handler'

describe('stringHandlers', () => {
it('#trim() trims spaces', () => {
expect(trim(' foo ')).toBe('foo')
})

it('#tabsToSpaces() replaces tabs with double space', () => {
expect(tabsToSpaces('\t\tfoo\tbar\t')).toBe(' foo bar ')
})

it('#relatify() removes current cwd path prefix from the output', () => {
const dirname = __dirname
const str = `
${dirname}/foo/bar.js
${dirname}/other/path.ts
`
const expected = `
/foo/bar.js
/other/path.ts
`

expect(relatify(str, dirname)).toBe(expected)
})


})

50 changes: 46 additions & 4 deletions src/test/ts/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,49 @@
import index from '../../main/ts'
import {readFileSync} from 'fs'
import {process} from '../../main/ts'

describe('', () => {
it('', () => {
expect(!!index).toBeTruthy()
describe('process', () => {
const target = 'src/test/snapshots/foo.json'
const dirname = __dirname
const stdout = `
\t${dirname}/rules/some-rules.ts
\t\t2:7 error 'name' is assigned a value but never used no-unused-vars
\t\t3:7 error 'name' is already defined no-redeclare
`
const cmd = `echo "${stdout}"`
const normalizedStdout = `/rules/some-rules.ts
2:7 error 'name' is assigned a value but never used no-unused-vars
3:7 error 'name' is already defined no-redeclare`

it('updates snapshot data with new stdout', async () => {
const result = await process({
cmd,
target,
update: true,
cmdOpts: { cwd: dirname},
})
expect(result).toBeTruthy()
expect(JSON.parse(readFileSync(target, 'utf-8')).stdout).toBe(normalizedStdout)
})

it('return true if new snapshot matches to the previous', async () => {
const result = await process({
cmd,
target,
cmdOpts: { cwd: dirname},
})

expect(result).toBeTruthy()
})

it('return false otherwise', async () => {
const result = await process({
cmd: 'echo baz quux',
target,
cmdOpts: { cwd: dirname},
})

expect(result).toBeFalsy()
})
})
Loading

0 comments on commit 497cb64

Please sign in to comment.