Skip to content

Commit

Permalink
a little util for making and saving snapshots
Browse files Browse the repository at this point in the history
First pass, possibly bad.  Need to play test it a little bit.
  • Loading branch information
isaacs committed Oct 31, 2017
1 parent e6ee1f8 commit 0f3dc9b
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 12 deletions.
51 changes: 51 additions & 0 deletions lib/snapshot.js
@@ -0,0 +1,51 @@
'use strict'

const writeFile = require('write-file-atomic')
const fs = require('fs')
const mkdirp = require('mkdirp')
const path = require('path')

class Snapshot {
constructor (test) {
this.indexes = new Map()
this.test = test
this.file = path.resolve(
process.cwd(),
'tap-snapshots',
this.test.fullname.replace(/[^a-zA-Z0-9\._\-]+/g, '-')
)
this.snapshot = null
}

// should only ever call _one_ of match/save
match (data, message) {
// XXX test line-by-line instead of all at once
const index = this.indexes.get(message) || 0
this.indexes.set(message, index + 1)
try {
this.snapshot = this.snapshot || require(this.file)
} catch (er) {
throw new Error('Snapshot file not found\n' +
'Run with TAP_SNAPSHOT=1 in the environment\n' +
'to create snapshot files')
}
return this.snapshot[message + '_' + index] === data
}

snap (data, message) {
const index = this.indexes.get(message) || 0
this.indexes.set(message, index + 1)
this.snapshot = this.snapshot || {}
this.snapshot[message + '_' + index] = data
}

save () {
const data = `'use strict'\n` + (
Object.keys(this.snapshot).map(s =>
`exports[\`${s}\`] = \`${this.snapshot[s]}\`\n`).join('\n'))
mkdirp.sync(path.dirname(this.file))
writeFile.sync(this.file, data, 'utf8')
}
}

module.exports = Snapshot
34 changes: 34 additions & 0 deletions lib/test.js
Expand Up @@ -25,6 +25,7 @@ const Pool = require('yapool')
const TestPoint = require('./point.js')
const parseTestArgs = require('./parse-test-args.js')
const loop = require('function-loop')
const path = require('path')

const extraFromError = require('./extra-from-error.js')
const tsame = require('tsame') // same thing, strict or not
Expand All @@ -46,6 +47,8 @@ const IMPLICIT = Symbol('implicit t.end()')
const EOF = Symbol('EOF')

const _end = Symbol('_end')
const _snapshot = Symbol('_snapshot')
const Snapshot = require('./snapshot.js')

const hasOwn = (obj, key) =>
Object.prototype.hasOwnProperty.call(obj, key)
Expand All @@ -56,6 +59,13 @@ class Test extends Base {
super(options)
this.pushedEnd = false
this.jobs = ownOr(options, 'jobs', 1)

this[_snapshot] = new Snapshot(this)
this.writeSnapshot = ownOrEnv(
options, 'snapshot', 'TAP_SNAPSHOT', true)
if (this.writeSnapshot)
this.teardown(_ => this[_snapshot].save())

this.subtests = []
this.pool = new Pool()
this.queue = ['TAP version 13\n']
Expand Down Expand Up @@ -928,6 +938,30 @@ class Test extends Base {
return this.notOk(tsame.strict(found, wanted), message, extra)
}

get fullname () {
return (this.parent ? this.parent.fullname
: path.basename(process.argv[1])) +
' ' + (this.name || '').trim()
}

matchSnapshot (found, message, extra) {
if (!this.currentAssert)
this.currentAssert = Test.prototype.matchSnapshot

if (message && typeof message === 'object')
extra = message, message = 'must match snapshot'

if (!extra)
extra = {}

// use notOk because snap doesn't return a truthy value
return this.writeSnapshot
? this.notOk(this[_snapshot].snap(found, message),
message, extra)
: this.ok(this[_snapshot].match(found, message),
message, extra)
}

match (found, wanted, message, extra) {
if (!this.currentAssert)
this.currentAssert = Test.prototype.match
Expand Down
32 changes: 20 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -37,6 +37,7 @@
"tmatch": "^3.1.0",
"trivial-deferred": "^1.0.1",
"tsame": "^1.1.2",
"write-file-atomic": "^2.3.0",
"yapool": "^1.0.0"
},
"keywords": [
Expand Down
44 changes: 44 additions & 0 deletions unit/snapshot.js
@@ -0,0 +1,44 @@
'use strict'
const t = require('../')
const Snapshot = require('../lib/snapshot.js')
const rimraf = require('rimraf')
const mkdirp = require('mkdirp')
const path = require('path')
const dir = path.resolve(__dirname, 'snapshot')
const fs = require('fs')

t.test('cleanup first', t => {
rimraf.sync(dir)
mkdirp.sync(dir)
process.chdir(dir)
t.end()
})

t.test('actual test', t => {
t.comment('not using subtests, because snapshots are per-test')

t.test('checking snapshot without creating throws', t => {
const s = new Snapshot(t)
t.throws(_ => s.match('asdf', 'asdf'))
t.end()
})

const s = new Snapshot(t)
t.comment('create some snapshots')
s.snap(fs.readFileSync(__filename, 'utf8'), 'this file')
s.snap('this is fine', 'a statement of acceptance')
s.save()

t.comment('now check that the snapshots are valid')
const ss = new Snapshot(t)
t.ok(ss.match(fs.readFileSync(__filename, 'utf8'), 'this file'))
t.ok(ss.match('this is fine', 'a statement of acceptance'))

t.end()
})

t.test('cleanup after', t => {
rimraf.sync(dir)
process.chdir(__dirname)
t.end()
})

0 comments on commit 0f3dc9b

Please sign in to comment.