造成什么原因"未捕获错误:写完后#34;在我的测试?

时间:2015-10-02 20:57:16

标签: javascript node.js promise mocha chai

我有以下代码:

var Promise = require('bluebird');
Promise.longStackTraces();
var path = require('path');
var fs = Promise.promisifyAll(require('fs-extra'));
var clone = require('nodegit').Clone.clone;
var tar = require('tar-fs');
var zlib = require('zlib');
var gzip = zlib.createGzip();
var globAsync = Promise.promisify(require('glob'));

module.exports = Archive;

function Archive(pkg) {
  var self = this;
  var tmp_dir_name = '.tmp';
  var code_dir_name = 'code';
  var files_dir_name = 'files';
  var output_dir_name = 'archives';
  var coverall_docs_dir_name = 'coverall_documents';

  // the archive's name (no extension):
  self.name = pkg.name;
  self.recipient_name = pkg.recipient_name;
  // path to letter.tex:
  self.tex_letter_path = path.resolve(pkg.files.letter);
  // path to resume.tex:
  self.tex_resume_path = path.resolve(pkg.files.resume);
  // path to merged.pdf (letter.pdf + resume.pdf):
  self.pdf_package_path = path.resolve(pkg.compiled_files.package);
  // temp dir where the archive is assembled:
  self.tmp_path = path.resolve(tmp_dir_name, pkg.name);
  // path to final archive:
  self.output_path = path.resolve(output_dir_name, self.name + '.tar.gz');
  // where to copy files to be added to the archive:
  self.files_path = path.resolve(tmp_dir_name, self.name, files_dir_name);
  // where the tex files are within the archive:
  self.coverall_docs_path = path.resolve(self.files_path, code_dir_name, coverall_docs_dir_name);
}

Archive.prototype.make = Promise.method(function() {
  var self = this;
  return self._prepareFilesDir()
    .then(self._copyFiles.bind(self))
    .then(self._writeArchive.bind(self))
    .then(self._delTmpDir.bind(self));
});

// ********************************
// * Private functions
// ********************************

Archive.prototype._prepareFilesDir = function() {
  var self = this;
  return fs.emptyDirAsync(self.tmp_path);
};

Archive.prototype._copyFiles = function() {
  var self = this;
  var sources = {
    tex_letter_path: path.resolve(self.tex_letter_path, '..'),
    tex_resume_path: path.resolve(self.tex_resume_path, '..'),
    tex_letter_shared_path: path.resolve(self.tex_letter_path, '../../shared'),
    pdf_package_path: self.pdf_package_path
  };
  var destinations = {
    letter_path: path.resolve(self.coverall_docs_path, 'coverletters', self.recipient_name.toLowerCase()),
    resume_path: path.resolve(self.coverall_docs_path, 'resume'),
    letter_shared_path: path.resolve(self.coverall_docs_path, 'coverletters/shared'),
    pdf_package_path: path.resolve(self.files_path, 'pdf', self.recipient_name.toLowerCase() + '.pdf'),
    coverall_repo_path: path.resolve(self.files_path, 'code/coverall')
  };
  var filters = {
    tex: function(filename) {
      var contains_dot = /\./gm;
      var hidden = /\/\./gm;
      var cls_or_tex_file = /\.(cls|tex)$/gm;
      var is_a_dir = !contains_dot.test(filename);
      var is_not_hidden = (contains_dot.test(filename) && !hidden.test(filename));
      var is_cls_or_tex = cls_or_tex_file.test(filename);
      // it doesn't contain a dot or it isn't a hidden file or it is a cls/tex file
      var is_allowed = is_a_dir || is_not_hidden || is_cls_or_tex;
      return is_allowed;
    },
    pdf: /[^\.].*\.pdf/
  };

  var copyLetter = function() {
    return fs.copyAsync(sources.tex_letter_path, destinations.letter_path, { filter: filters.tex });
  };
  function copyShared() {
    return fs.copyAsync(sources.tex_letter_shared_path, destinations.letter_shared_path, { filter: filters.tex });
  }
  function copyResume() {
    return fs.copyAsync(sources.tex_resume_path, destinations.resume_path, { filter: filters.tex });
  }
  function copyPdf() {
    return fs.copyAsync(sources.pdf_package_path, destinations.pdf_package_path, { filter: filters.pdf });
  }
  function copyJs() {
    return clone('https://github.com/coaxial/coverall.git', destinations.coverall_repo_path);
  }


  return Promise.all([
      copyLetter(),
      copyShared(),
      copyResume(),
      copyPdf(),
      copyJs()
  ]);
};

Archive.prototype._writeArchive = function() {
  var self = this;
  var archive_dir_path = path.resolve(self.output_path, '..');
  var tarPromise = function() {
    return new Promise(function(resolve, reject) {
      tar.pack(self.files_path)
        .pipe(gzip)
        .pipe(fs.createWriteStream(self.output_path))
        .on('error', reject)
        .on('finish', resolve);
    });
  };

  return fs.ensureDirAsync(archive_dir_path)
    .then(tarPromise);
};

Archive.prototype._delTmpDir = function() {
  var self = this;

  return fs.removeAsync(self.tmp_path);
};

我正在测试它:

/*eslint-env mocha */
var chai = require('chai');
var chaiAsPromised = require("chai-as-promised");
var expect = chai.expect;
var Promise = require('bluebird');
Promise.longStackTraces();
var Archive = require('../lib/archive');
var path = require('path');
var fs = Promise.promisifyAll(require('fs-extra'));
var globAsync = Promise.promisify(require('glob'));
var tar = require('tar-fs');
var zlib = Promise.promisifyAll(require('zlib'));
var _ = require('lodash');

chai.use(chaiAsPromised);

describe.only('Archive', function() {
  var pkg;

  beforeEach(function() {
    pkg = {
      name: 'test_0790feebb1',
      recipient_name: 'Test',
      files: {
        letter: '../coverall_documents/coverletters/test/letter.tex',
        resume: '../coverall_documents/resume/resume.tex'
      },
      compiled_files: {
        package: '../coverall_documents/coverletters/test/test.pdf'
      }
    };
  });

  // after(function() {
  //   return Promise.all([
  //       'archives/test*',
  //       'test/.tmp'
  //   ].map(function(glob_pattern) {
  //     return globAsync(glob_pattern)
  //       .each(function(filename) {
  //         // make every file writeable so the git packfiles can be removed
  //         return fs.chmodAsync(filename, '755')
  //           .then(function() { fs.removeAsync(filename); });
  //       })
  //   }));
  // });

  describe('#make', function() {
    it('creates an archive', function() {
      var modified_pkg = _.cloneDeep(pkg);
      modified_pkg.name = 'test_0000000001';
      var archive_location = path.resolve('archives', modified_pkg.name + '.tar.gz');
      var test_archive = new Archive(modified_pkg);

      return test_archive.make()
        .then(function() { return fs.statAsync(archive_location); })
        .then(function(file) { return expect(file).to.exist; })
        .catch(function(e) { return expect(e).to.not.exist; });
    });

    it('creates a gzip compressed archive', function() {
      var modified_pkg = _.cloneDeep(pkg);
      modified_pkg.name = 'test_0000000002';
      var archive_location = path.resolve('archives', modified_pkg.name + '.tar.gz');
      var test_archive = new Archive(modified_pkg);

      // inspired from https://github.com/mafintosh/gunzip-maybe/blob/master/index.js#L6-L11
      var isGzipped = function(data) {
        var GZIP_MAGIC_BYTES = [0x1f, 0x8b];
        var DEFLATE_COMPRESSION_METHOD = 0x08;
        var buffer = data[1];

        if (buffer[0] !== GZIP_MAGIC_BYTES[0] && buffer[1] !== GZIP_MAGIC_BYTES[1]) return false;
        if (buffer[2] !== DEFLATE_COMPRESSION_METHOD) return false;
        return true;
      };

      return test_archive.make()
        .then(function() { return fs.openAsync(archive_location, 'r'); })
        .then(function(fd) { 
          var buffer = new Buffer(10);
          var buffer_offset = 0;
          var buffer_length = 10;
          var file_position = 0;
          return fs.readAsync(fd, buffer, buffer_offset, buffer_length, file_position);
        })
      .then(function(data) { console.log('data', data); return data; })
        .then(function(data) { return expect(isGzipped(data)).to.be.true; })
    });

    it('has the correct directory structure', function() {
      var modified_pkg = _.cloneDeep(pkg);
      modified_pkg.name = 'test_0000000003';
      var archive_location = path.resolve('archives', modified_pkg.name + '.tar.gz');
      var test_archive = new Archive(modified_pkg);
      var tmp_extract_path = path.resolve('test/.tmp');

      var tarPromise = function(archive_path) {
        return new Promise(function(resolve, reject) {
          fs.createReadStream(archive_path)
            .pipe(zlib.Unzip())
            .pipe(tar.extract(tmp_extract_path))
            .on('error', reject)
            .on('finish', resolve);
        })
      };

      var verifyDir = function() {
        return Promise.all([
            'code',
            'pdf',
            'code/coverall',
            'code/coverall_documents',
            'code/coverall_documents/coverletters',
            'code/coverall_documents/coverletters/test',
            'code/coverall_documents/coverletters/shared',
            'code/coverall_documents/resume',
            'code/coverall_documents/coverletters'
        ].map(function(subpath) {
          return expect(fs.statAsync(path.resolve(tmp_extract_path, subpath)))
            .to.be.fulfilled;
        }))
      };

      return test_archive.make()
        .then(function() { return tarPromise(archive_location); })
        .then(function() { return verifyDir(); });
    });

    it('removes the temporary dir', function() {
      var modified_pkg = _.cloneDeep(pkg);
      modified_pkg.name = 'test_0000000004';
      var archive_location = path.resolve('archives', modified_pkg.name + '.tar.gz');
      var test_archive = new Archive(modified_pkg);
      var tmp_dir = path.resolve('.tmp');

      return test_archive.make()
        .then(function() { return expect(fs.statAsync(tmp_dir)).to.be.rejected; });
    });
  });
});

结果是:

$ mocha test


  Archive
    #make
      ✓ creates an archive (644ms)
      1) creates a gzip compressed archive
      2) has the correct directory structure
      3) removes the temporary dir


  1 passing (2s)
  3 failing

  1) Archive #make creates a gzip compressed archive:
     Uncaught Error: write after end
      at writeAfterEnd (_stream_writable.js:167:12)
      at Gzip.Writable.write (_stream_writable.js:214:5)
      at ondata (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:574:20)
      at readableAddChunk (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:198:16)
      at Readable.push (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:162:10)
      at Pack._encode (node_modules/tar-fs/node_modules/tar-stream/pack.js:154:17)
      at Pack.entry (node_modules/tar-fs/node_modules/tar-stream/pack.js:100:10)
      at onstat (node_modules/tar-fs/index.js:108:19)
      at node_modules/tar-fs/index.js:40:9
      at FSReqWrap.oncomplete (fs.js:95:15)

  2) Archive #make has the correct directory structure:
     AssertionError: expected false to be true
      at Context.<anonymous> (test/archive_spec.js:96:10)

  3) Archive #make removes the temporary dir:
     Uncaught Error: write after end
      at writeAfterEnd (_stream_writable.js:167:12)
      at Gzip.Writable.write (_stream_writable.js:214:5)
      at ondata (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:574:20)
      at readableAddChunk (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:198:16)
      at Readable.push (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:162:10)
      at Pack._encode (node_modules/tar-fs/node_modules/tar-stream/pack.js:154:17)
      at Pack.entry (node_modules/tar-fs/node_modules/tar-stream/pack.js:100:10)
      at onstat (node_modules/tar-fs/index.js:108:19)
      at node_modules/tar-fs/index.js:40:9
      at FSReqWrap.oncomplete (fs.js:95:15)

我怀疑是竞争状况,所以我评论了after区块,看看它是否会有所不同,但它没有。

我不明白Uncaught Error: write after end的含义以及堆栈跟踪无法使用的原因,即使我使用的是Promise.longStackTraces()。导致此错误的原因是什么?

我的测试对于他们正在做的事情看起来过于复杂,我在实例化不同的test_archive对象时多次重复代码。我怎么能重构它们呢?

1 个答案:

答案 0 :(得分:4)

您正在尝试重复使用同样的gzip实例,但该实例无法正常工作。这也解释了为什么第一次测试工作正常。

然后将var gzip = zlib.createGzip();行移到Archive.prototype._writeArchive函数内。