text: /*jshint asi:true */
  
  var debug           //= true;
  var test            = debug  ? function () {} : require('tap').test
  var test_           = !debug ? function () {} : require('tap').test
    , path            = require('path')
    , fs              = require('fs')
    , util            = require('util')
    , TransformStream = require('readable-stream').Transform
    , through         = require('through2')
    , proxyquire      = require('proxyquire')
    , streamapi       = require('../stream-api')
    , readdirp        = require('..')
    , root            = path.join(__dirname, 'bed')
    , totalDirs       = 6
    , totalFiles      = 12
    , ext1Files       = 4
    , ext2Files       = 3
    , ext3Files       = 2
    ;
    
  // see test/readdirp.js for test bed layout
  
  function opts (extend) {
    var o = { root: root };
  
    if (extend) {
      for (var prop in extend) {
        o[prop] = extend[prop];
      }
    }
    return o;
  }
  
  function capture () {
    var result = { entries: [], errors: [], ended: false }
      , dst = new TransformStream({ objectMode: true });
  
    dst._transform = function (entry, _, cb) {
      result.entries.push(entry);
      cb();
    }
  
    dst._flush = function (cb) {
      result.ended = true;
      this.push(result); 
      cb();
    }
  
    return dst;
  }
  
  test('\nintegrated', function (t) {
    t.test('\n# reading root without filter', function (t) {
      t.plan(2);
  
      readdirp(opts())
        .on('error', function (err) {
          t.fail('should not throw error', err);
        })
        .pipe(capture())
        .pipe(through.obj(
          function (result, _ , cb) { 
            t.equals(result.entries.length, totalFiles, 'emits all files');
            t.ok(result.ended, 'ends stream');
            t.end();
            cb();
          }
        ));
    })
  
    t.test('\n# normal: ["*.ext1", "*.ext3"]', function (t) {
      t.plan(2);
  
      readdirp(opts( { fileFilter: [ '*.ext1', '*.ext3' ] } ))
        .on('error', function (err) {
          t.fail('should not throw error', err);
        })
        .pipe(capture())
        .pipe(through.obj(
          function (result, _ , cb) { 
            t.equals(result.entries.length, ext1Files + ext3Files, 'all ext1 and ext3 files');
            t.ok(result.ended, 'ends stream');
            t.end();
            cb();
          }
        ))
    })
  
    t.test('\n# files only', function (t) {
      t.plan(2);
  
      readdirp(opts( { entryType: 'files' } ))
        .on('error', function (err) {
          t.fail('should not throw error', err);
        })
        .pipe(capture())
        .pipe(through.obj(
          function (result, _ , cb) { 
            t.equals(result.entries.length, totalFiles, 'returned files');
            t.ok(result.ended, 'ends stream');
            t.end();
            cb();
          }
        ))
    })
  
    t.test('\n# directories only', function (t) {
      t.plan(2);
  
      readdirp(opts( { entryType: 'directories' } ))
        .on('error', function (err) {
          t.fail('should not throw error', err);
        })
        .pipe(capture())
        .pipe(through.obj(
          function (result, _ , cb) { 
            t.equals(result.entries.length, totalDirs, 'returned directories');
            t.ok(result.ended, 'ends stream');
            t.end();
            cb();
          }
        ))
    })
  
    t.test('\n# both directories + files', function (t) {
      t.plan(2);
  
      readdirp(opts( { entryType: 'both' } ))
        .on('error', function (err) {
          t.fail('should not throw error', err);
        })
        .pipe(capture())
        .pipe(through.obj(
          function (result, _ , cb) { 
            t.equals(result.entries.length, totalDirs + totalFiles, 'returned everything');
            t.ok(result.ended, 'ends stream');
            t.end();
            cb();
          }
        ))
    })
  
    t.test('\n# directory filter with directories only', function (t) {
      t.plan(2);
  
      readdirp(opts( { entryType: 'directories', directoryFilter: [ 'root_dir1', '*dir1_subdir1' ] } ))
        .on('error', function (err) {
          t.fail('should not throw error', err);
        })
        .pipe(capture())
        .pipe(through.obj(
          function (result, _ , cb) {
            t.equals(result.entries.length, 2, 'two directories');
            t.ok(result.ended, 'ends stream');
            t.end();
            cb();
          }
        ))
    })
  
    t.test('\n# directory and file filters with both entries', function (t) {
      t.plan(2);
  
      readdirp(opts( { entryType: 'both', directoryFilter: [ 'root_dir1', '*dir1_subdir1' ], fileFilter: [ '!*.ext1' ] } ))
        .on('error', function (err) {
          t.fail('should not throw error', err);
        })
        .pipe(capture())
        .pipe(through.obj(
          function (result, _ , cb) {
            t.equals(result.entries.length, 6, '2 directories and 4 files');
            t.ok(result.ended, 'ends stream');
            t.end();
            cb();
          }
        ))
    })
  
    t.test('\n# negated: ["!*.ext1", "!*.ext3"]', function (t) {
      t.plan(2);
  
      readdirp(opts( { fileFilter: [ '!*.ext1', '!*.ext3' ] } ))
        .on('error', function (err) {
          t.fail('should not throw error', err);
        })
        .pipe(capture())
        .pipe(through.obj(
          function (result, _ , cb) { 
            t.equals(result.entries.length, totalFiles - ext1Files - ext3Files, 'all but ext1 and ext3 files');
            t.ok(result.ended, 'ends stream');
            t.end();
          }
        ))
    })
  
    t.test('\n# no options given', function (t) {
      t.plan(1);
      readdirp()
        .on('error', function (err) {
          t.similar(err.toString() , /Need to pass at least one argument/ , 'emits meaningful error');
          t.end();
        })
    })
  
    t.test('\n# mixed: ["*.ext1", "!*.ext3"]', function (t) {
      t.plan(1);
  
      readdirp(opts( { fileFilter: [ '*.ext1', '!*.ext3' ] } ))
        .on('error', function (err) {
          t.similar(err.toString() , /Cannot mix negated with non negated glob filters/ , 'emits meaningful error');
          t.end();
        })
    })
  })
  
  
  test('\napi separately', function (t) {
  
    t.test('\n# handleError', function (t) {
      t.plan(1);
  
      var api = streamapi()
        , warning = new Error('some file caused problems');
  
      api.stream
        .on('warn', function (err) {
          t.equals(err, warning, 'warns with the handled error');
        })
      api.handleError(warning);
    })
  
    t.test('\n# when stream is paused and then resumed', function (t) {
      t.plan(6);
      var api = streamapi()
        , resumed = false
        , fatalError = new Error('fatal!')
        , nonfatalError = new Error('nonfatal!')
        , processedData = 'some data'
        ;
  
      api.stream
        .on('warn', function (err) {
          t.equals(err, nonfatalError, 'emits the buffered warning');
          t.ok(resumed, 'emits warning only after it was resumed');
        })
        .on('error', function (err) {
          t.equals(err, fatalError, 'emits the buffered fatal error');
          t.ok(resumed, 'emits errors only after it was resumed');
        })
        .on('data', function (data) {
          t.equals(data, processedData, 'emits the buffered data');
          t.ok(resumed, 'emits data only after it was resumed');
        })
        .pause()
      
      api.processEntry(processedData);
      api.handleError(nonfatalError);
      api.handleFatalError(fatalError);
    
      setTimeout(function () {
        resumed = true;
        api.stream.resume();
      }, 1)
    })
  
    t.test('\n# when a stream is paused it stops walking the fs', function (t) {
      var resumed = false,
        mockedAPI = streamapi();
  
      mockedAPI.processEntry = function (entry) {
        if (!resumed) t.notOk(true, 'should not emit while paused')
        t.ok(entry, 'emitted while resumed')
      }.bind(mockedAPI.stream)
  
      function wrapper () {
        return mockedAPI
      }
  
      var readdirp = proxyquire('../readdirp', {'./stream-api': wrapper})
        , stream = readdirp(opts())
        .on('error', function (err) {
          t.fail('should not throw error', err);
        })
        .on('end', function () {
          t.end()
        })
        .pause();
  
      setTimeout(function () {
        resumed = true;
        stream.resume();
      }, 5)
    })
  
    t.test('\n# when a stream is destroyed, it emits "closed", but no longer emits "data", "warn" and "error"', function (t) {
      var api = streamapi()
        , fatalError = new Error('fatal!')
        , nonfatalError = new Error('nonfatal!')
        , processedData = 'some data'
        , plan = 0;
  
      t.plan(6)
      var stream = api.stream
        .on('warn', function (err) {
          t.ok(!stream._destroyed, 'emits warning until destroyed');
        })
        .on('error', function (err) {
          t.ok(!stream._destroyed, 'emits errors until destroyed');
        })
        .on('data', function (data) {
          t.ok(!stream._destroyed, 'emits data until destroyed');
        })
        .on('close', function () {
          t.ok(stream._destroyed, 'emits close when stream is destroyed');
        })
      
  
      api.processEntry(processedData);
      api.handleError(nonfatalError);
      api.handleFatalError(fatalError);
  
      setTimeout(function () {
        stream.destroy()
  
        t.notOk(stream.readable, 'stream is no longer readable after it is destroyed')
  
        api.processEntry(processedData);
        api.handleError(nonfatalError);
        api.handleFatalError(fatalError);
  
        process.nextTick(function () {
          t.pass('emits no more data, warn or error events after it was destroyed')  
          t.end();
        })
      }, 10)
    })
  })
  