-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add configurable output normalization
- Loading branch information
1 parent
328eedd
commit 497cb64
Showing
9 changed files
with
409 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ' ') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"}}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
|
||
|
||
}) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
}) | ||
}) |
Oops, something went wrong.