javascript - 是什么导致我的测试中出现 "Uncaught Error: write after end"?

标签 javascript node.js promise mocha.js 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 block ,看看它是否会产生任何影响,但事实并非如此。

我不明白未捕获错误:结束后写入是什么意思,也不明白为什么堆栈跟踪不可用,即使我使用的是Promise.longStackTraces()。是什么导致了这个错误?

我的测试看起来过于复杂,并且在实例化不同的 test_archive 对象时,我多次重复代码。我该如何重构它们?

最佳答案

您尝试重复使用相同的 gzip 实例,但这是行不通的。这也解释了为什么第一个测试工作得很好。

因此,将 var gzip = zlib.createGzip(); 行移至 Archive.prototype._writeArchive 函数内部。

关于javascript - 是什么导致我的测试中出现 "Uncaught Error: write after end"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32915744/

相关文章:

node.js - Sequelize 创建关联的对象不起作用

json - 为什么express不设置json内容类型?

javascript - 需要一些帮助来使用 .then 的 promise 函数

javascript - 如何获取 KineticJS 舞台的左侧和顶部坐标?

javascript - 抑制 Google Chrome 中的 "Rats! WebGL hit a snag."错误栏

javascript - 如何只允许某些组件滚动并保持某些 React 组件固定?

javascript - 在 Javascript 中等待函数时捕获错误的正确方法

javascript - 向国际用户显示消息

node.js - node.js 客户端中的 Windows 集成身份验证

javascript - 当另一个 Firebase 函数已经在运行时再次发生 onCreate 事件时会发生什么?