├── .eslintignore ├── .eslintrc ├── index.js ├── package.json ├── test └── index.js ├── through.js ├── readme.md ├── sink-test.js ├── source-test.js └── .gitignore /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["minlint"] 3 | } 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | source: require('./source-test'), 3 | sink: require('./sink-test'), 4 | through: require('./through') 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pull-stream-spec", 3 | "version": "0.0.3", 4 | "description": "Tests for pull streams", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "eslint **/*.js && node test/index.js", 8 | "preversion": "npm test", 9 | "postversion": "git push && git push --tags && npm publish" 10 | }, 11 | "dependencies": { 12 | "pull-stream": "^3.4.5" 13 | }, 14 | "devDependencies": { 15 | "eslint": "^3.5.0", 16 | "eslint-config-minlint": "^1.0.2", 17 | "pull-cat": "^1.1.11", 18 | "pull-stream": "^3.4.5", 19 | "tape": "^4.6.0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/nichoth/pull-stream-spec.git" 24 | }, 25 | "keywords": [ 26 | "pull-stream", 27 | "pull", 28 | "stream", 29 | "spec", 30 | "test" 31 | ], 32 | "author": "nichoth", 33 | "license": "ISC", 34 | "bugs": { 35 | "url": "https://github.com/nichoth/pull-stream-spec/issues" 36 | }, 37 | "homepage": "https://github.com/nichoth/pull-stream-spec#readme" 38 | } 39 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var S = require('pull-stream') 3 | S.cat = require('pull-cat') 4 | var spec = require('../') 5 | 6 | // test a source stream 7 | spec.source(test, S.values.bind(S, [1,2,3])) 8 | 9 | // test a sink stream 10 | function mySink () { 11 | return S.drain(function onData () {}, function onEnd (err) { 12 | // caught error 13 | }) 14 | } 15 | spec.sink(test, mySink) 16 | 17 | // compose a pipeline with a through stream that makes assertions 18 | // about how it is called 19 | test('I want to test this sink with special source streams', function (t) { 20 | t.plan(1) 21 | 22 | // emit 3 values then error 23 | var testSource = S.cat([ 24 | S.values([1,2,3]), 25 | S.error(new Error('test')) 26 | ]) 27 | 28 | var mySink = S.drain(function onData (d) {}, function onEnd (err) { 29 | // caught error, do nothing 30 | }) 31 | 32 | t.doesNotThrow( 33 | S.bind(null, 34 | testSource, 35 | spec.through(), 36 | mySink 37 | ), 38 | 'should not throw' 39 | ) 40 | }) 41 | -------------------------------------------------------------------------------- /through.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | 3 | module.exports = function Through () { 4 | var isResolving = false 5 | var ended = false 6 | var errd = false 7 | var aborted = false 8 | return function sink (source) { 9 | return function newSource (abort, next) { 10 | assert(!aborted, 'should not call source after aborting') 11 | assert(!isResolving, 'should not call source before it returns') 12 | aborted = abort || aborted 13 | isResolving = true 14 | source(abort, function onNext (end, data) { 15 | isResolving = false 16 | assert(!ended, 'should not emit more after ending') 17 | assert(!errd, 'should not emit more after error') 18 | if (end === true) { 19 | ended = true 20 | return next(end) 21 | } 22 | if (end) { 23 | errd = true 24 | return next(end) 25 | } 26 | next(end, data) 27 | }) 28 | } 29 | } 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # pull stream spec 2 | What is a pull stream? 3 | 4 | The source stream 5 | 6 | * if it is passed a truthy `abort` param, it must call the callback with a truthy `end` param 7 | * It must not call the callback more than once (before it has been called again) 8 | 9 | The sink stream 10 | 11 | * If the callback gets a truthy `end` param, it must not call source again 12 | * It must not call the source multiple times before getting a callback 13 | 14 | Both sources and sinks should work whether they are called synchronously 15 | or asynchronously. 16 | 17 | 18 | ## install 19 | 20 | npm install pull-stream-spec 21 | 22 | ## example 23 | 24 | ```js 25 | var test = require('tape') 26 | var S = require('pull-stream') 27 | S.cat = require('pull-cat') 28 | var spec = require('../') 29 | 30 | // test a source stream 31 | spec.source(test, S.values.bind(S, [1,2,3])) 32 | 33 | // test a sink stream 34 | function mySink () { 35 | return S.drain(function onData () {}, function onEnd (err) { 36 | // caught error 37 | }) 38 | } 39 | spec.sink(test, mySink) 40 | 41 | // compose a pipeline with a through stream that makes assertions 42 | // about how it is called. Since it is a proxy to a source and sink, 43 | // it could be used to test either. 44 | test('I want to test this sink with special source streams', function (t) { 45 | t.plan(1) 46 | 47 | // emit 3 values then error 48 | var testSource = S.cat([ 49 | S.values([1,2,3]), 50 | S.error(new Error('test')) 51 | ]) 52 | 53 | var mySink = S.drain(function onData (d) {}, function onEnd (err) { 54 | // caught error, do nothing 55 | }) 56 | 57 | t.doesNotThrow( 58 | S.bind(null, 59 | testSource, 60 | spec.through(), 61 | mySink 62 | ), 63 | 'should not throw' 64 | ) 65 | }) 66 | ``` 67 | -------------------------------------------------------------------------------- /sink-test.js: -------------------------------------------------------------------------------- 1 | // the sink takes a source stream and calls it with a cb 2 | // the cb gets passed `end` and `data` params from the source stream 3 | // if the callback gets a truthy end param, it must not call source again 4 | // if end is falsy, the cb can call source again, which signals that 5 | // the sink is ready for a new event 6 | function SinkTest (test, sink) { 7 | test('Source emits end', function (t) { 8 | sink()(End(t)) 9 | }) 10 | 11 | test('Source emits error', function (t) { 12 | sink()(Err(t)) 13 | }) 14 | 15 | test('Async source', function (t) { 16 | sink()(AsyncSource(t)) 17 | }) 18 | } 19 | 20 | function End (t, count) { 21 | count = count || 0 22 | var ended = false 23 | var i = 0 24 | function source (abort, next) { 25 | if (abort) t.fail('sink aborted before test finished') 26 | if (ended) t.fail('should not call source after end') 27 | if (i === count) { 28 | ended = true 29 | next(true) 30 | return t.end() 31 | } 32 | next(null, i++) 33 | } 34 | return source 35 | } 36 | 37 | function Err (t, count) { 38 | count = count || 0 39 | var errd = false 40 | var i = 0 41 | function source (abort, next) { 42 | if (abort) t.fail('sink aborted before test finished') 43 | if (errd) t.fail('should not call source after error') 44 | if (i === count) { 45 | errd = true 46 | next(new Error('test')) 47 | return t.end() 48 | } 49 | next(null, i++) 50 | } 51 | return source 52 | } 53 | 54 | function AsyncSource (t, count) { 55 | count = count || 0 56 | var ended = false 57 | var isResolving = false 58 | var i = 0 59 | return function asyncSource (abort, emitNext) { 60 | if (abort) t.fail('sink aborted before the test finished') 61 | if (ended) t.fail('should not call source after end') 62 | if (isResolving) t.fail('source was called out of turn') 63 | if (i === count) return process.nextTick(function () { 64 | ended = true 65 | emitNext(true) 66 | t.end() 67 | }) 68 | isResolving = true 69 | process.nextTick(function () { 70 | isResolving = false 71 | emitNext(null, i++) 72 | }) 73 | } 74 | } 75 | 76 | module.exports = SinkTest 77 | module.exports.End = End 78 | module.exports.Err = Err 79 | module.exports.AsyncSource = AsyncSource 80 | -------------------------------------------------------------------------------- /source-test.js: -------------------------------------------------------------------------------- 1 | var S = require('pull-stream') 2 | 3 | // the source stream takes two params: abort and cb 4 | // it calls the cb with two params: (end, value) 5 | // the source stream has a contract: if it is passed a truthy `abort` 6 | // param, it must call the callback with a truthy `end` param 7 | function SourceTest (test, source) { 8 | test('sink that aborts', function (t) { 9 | AbortingSink(t)(source()) 10 | }) 11 | 12 | test('Async Sink', function (t) { 13 | AsyncSink(t)(source()) 14 | }) 15 | 16 | test('Slow Sink', function (t) { 17 | SlowSink(t)(source()) 18 | }) 19 | } 20 | 21 | function AbortingSink (t, count) { 22 | t.plan(1) 23 | count = count || 0 24 | var i = 0 25 | return S.drain(function onData (d) { 26 | if (i === count) return false 27 | i++ 28 | }, function onEnd (err) { 29 | t.pass('should end after the sink aborts') 30 | }) 31 | } 32 | 33 | function AsyncSink (t) { 34 | t.plan(1) 35 | return function asyncSink (source) { 36 | var isResolving = false 37 | function onNext (end, data) { 38 | if (isResolving) { 39 | t.fail('should not callback before the sink is ready') 40 | } 41 | isResolving = true 42 | if (end) return t.pass('should not callback out of turn') 43 | process.nextTick(function () { 44 | isResolving = false 45 | source(null, onNext) 46 | }) 47 | } 48 | process.nextTick(function () { 49 | source(null, onNext) 50 | }) 51 | } 52 | } 53 | 54 | function SlowSink (t, time) { 55 | t.plan(1) 56 | time = time || 50 57 | return function asyncSink (source) { 58 | var isResolving = false 59 | setTimeout(function () { 60 | source(null, function onNext (end, data) { 61 | if (isResolving) { 62 | t.fail('should not callback before the sink is ready') 63 | } 64 | isResolving = true 65 | if (end) return t.pass('should not callback out of turn') 66 | setTimeout(function () { 67 | isResolving = false 68 | source(null, onNext) 69 | }, time) 70 | }) 71 | }, time) 72 | } 73 | } 74 | 75 | SourceTest.AbortingSink = AbortingSink 76 | SourceTest.AsyncSink = AsyncSink 77 | SourceTest.SlowSink = SlowSink 78 | module.exports = SourceTest 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/7751c25c6662ce6f9dc50f014e37156298ccf065/Global/SublimeText.gitignore 2 | 3 | # cache files for sublime text 4 | *.tmlanguage.cache 5 | *.tmPreferences.cache 6 | *.stTheme.cache 7 | 8 | # workspace files are user-specific 9 | *.sublime-workspace 10 | 11 | # project files should be checked into the repository, unless a significant 12 | # proportion of contributors will probably not be using SublimeText 13 | # *.sublime-project 14 | 15 | # sftp configuration file 16 | sftp-config.json 17 | 18 | # Package control specific files 19 | Package Control.last-run 20 | Package Control.ca-list 21 | Package Control.ca-bundle 22 | Package Control.system-ca-bundle 23 | Package Control.cache/ 24 | Package Control.ca-certs/ 25 | bh_unicode_properties.cache 26 | 27 | # Sublime-github package stores a github token in this file 28 | # https://packagecontrol.io/packages/sublime-github 29 | GitHub.sublime-settings 30 | 31 | 32 | ### https://raw.github.com/github/gitignore/7751c25c6662ce6f9dc50f014e37156298ccf065/Global/OSX.gitignore 33 | 34 | *.DS_Store 35 | .AppleDouble 36 | .LSOverride 37 | 38 | # Icon must end with two \r 39 | Icon 40 | 41 | # Thumbnails 42 | ._* 43 | 44 | # Files that might appear in the root of a volume 45 | .DocumentRevisions-V100 46 | .fseventsd 47 | .Spotlight-V100 48 | .TemporaryItems 49 | .Trashes 50 | .VolumeIcon.icns 51 | .com.apple.timemachine.donotpresent 52 | 53 | # Directories potentially created on remote AFP share 54 | .AppleDB 55 | .AppleDesktop 56 | Network Trash Folder 57 | Temporary Items 58 | .apdisk 59 | 60 | 61 | ### https://raw.github.com/github/gitignore/7751c25c6662ce6f9dc50f014e37156298ccf065/Node.gitignore 62 | 63 | # Logs 64 | logs 65 | *.log 66 | npm-debug.log* 67 | 68 | # Runtime data 69 | pids 70 | *.pid 71 | *.seed 72 | 73 | # Directory for instrumented libs generated by jscoverage/JSCover 74 | lib-cov 75 | 76 | # Coverage directory used by tools like istanbul 77 | coverage 78 | 79 | # nyc test coverage 80 | .nyc_output 81 | 82 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 83 | .grunt 84 | 85 | # node-waf configuration 86 | .lock-wscript 87 | 88 | # Compiled binary addons (http://nodejs.org/api/addons.html) 89 | build/Release 90 | 91 | # Dependency directories 92 | node_modules 93 | jspm_packages 94 | 95 | # Optional npm cache directory 96 | .npm 97 | 98 | # Optional REPL history 99 | .node_repl_history 100 | 101 | 102 | ### https://raw.github.com/github/gitignore/7751c25c6662ce6f9dc50f014e37156298ccf065/Sass.gitignore 103 | 104 | .sass-cache/ 105 | *.css.map 106 | 107 | 108 | bundle.js 109 | --------------------------------------------------------------------------------