是什么导致我的测试中出现“未捕获错误:结束后写入”?

spr*_*ded 0 javascript mocha.js node.js promise chai

我有以下代码:

\n\n
var Promise = require(\'bluebird\');\nPromise.longStackTraces();\nvar path = require(\'path\');\nvar fs = Promise.promisifyAll(require(\'fs-extra\'));\nvar clone = require(\'nodegit\').Clone.clone;\nvar tar = require(\'tar-fs\');\nvar zlib = require(\'zlib\');\nvar gzip = zlib.createGzip();\nvar globAsync = Promise.promisify(require(\'glob\'));\n\nmodule.exports = Archive;\n\nfunction Archive(pkg) {\n  var self = this;\n  var tmp_dir_name = \'.tmp\';\n  var code_dir_name = \'code\';\n  var files_dir_name = \'files\';\n  var output_dir_name = \'archives\';\n  var coverall_docs_dir_name = \'coverall_documents\';\n\n  // the archive\'s name (no extension):\n  self.name = pkg.name;\n  self.recipient_name = pkg.recipient_name;\n  // path to letter.tex:\n  self.tex_letter_path = path.resolve(pkg.files.letter);\n  // path to resume.tex:\n  self.tex_resume_path = path.resolve(pkg.files.resume);\n  // path to merged.pdf (letter.pdf + resume.pdf):\n  self.pdf_package_path = path.resolve(pkg.compiled_files.package);\n  // temp dir where the archive is assembled:\n  self.tmp_path = path.resolve(tmp_dir_name, pkg.name);\n  // path to final archive:\n  self.output_path = path.resolve(output_dir_name, self.name + \'.tar.gz\');\n  // where to copy files to be added to the archive:\n  self.files_path = path.resolve(tmp_dir_name, self.name, files_dir_name);\n  // where the tex files are within the archive:\n  self.coverall_docs_path = path.resolve(self.files_path, code_dir_name, coverall_docs_dir_name);\n}\n\nArchive.prototype.make = Promise.method(function() {\n  var self = this;\n  return self._prepareFilesDir()\n    .then(self._copyFiles.bind(self))\n    .then(self._writeArchive.bind(self))\n    .then(self._delTmpDir.bind(self));\n});\n\n// ********************************\n// * Private functions\n// ********************************\n\nArchive.prototype._prepareFilesDir = function() {\n  var self = this;\n  return fs.emptyDirAsync(self.tmp_path);\n};\n\nArchive.prototype._copyFiles = function() {\n  var self = this;\n  var sources = {\n    tex_letter_path: path.resolve(self.tex_letter_path, \'..\'),\n    tex_resume_path: path.resolve(self.tex_resume_path, \'..\'),\n    tex_letter_shared_path: path.resolve(self.tex_letter_path, \'../../shared\'),\n    pdf_package_path: self.pdf_package_path\n  };\n  var destinations = {\n    letter_path: path.resolve(self.coverall_docs_path, \'coverletters\', self.recipient_name.toLowerCase()),\n    resume_path: path.resolve(self.coverall_docs_path, \'resume\'),\n    letter_shared_path: path.resolve(self.coverall_docs_path, \'coverletters/shared\'),\n    pdf_package_path: path.resolve(self.files_path, \'pdf\', self.recipient_name.toLowerCase() + \'.pdf\'),\n    coverall_repo_path: path.resolve(self.files_path, \'code/coverall\')\n  };\n  var filters = {\n    tex: function(filename) {\n      var contains_dot = /\\./gm;\n      var hidden = /\\/\\./gm;\n      var cls_or_tex_file = /\\.(cls|tex)$/gm;\n      var is_a_dir = !contains_dot.test(filename);\n      var is_not_hidden = (contains_dot.test(filename) && !hidden.test(filename));\n      var is_cls_or_tex = cls_or_tex_file.test(filename);\n      // it doesn\'t contain a dot or it isn\'t a hidden file or it is a cls/tex file\n      var is_allowed = is_a_dir || is_not_hidden || is_cls_or_tex;\n      return is_allowed;\n    },\n    pdf: /[^\\.].*\\.pdf/\n  };\n\n  var copyLetter = function() {\n    return fs.copyAsync(sources.tex_letter_path, destinations.letter_path, { filter: filters.tex });\n  };\n  function copyShared() {\n    return fs.copyAsync(sources.tex_letter_shared_path, destinations.letter_shared_path, { filter: filters.tex });\n  }\n  function copyResume() {\n    return fs.copyAsync(sources.tex_resume_path, destinations.resume_path, { filter: filters.tex });\n  }\n  function copyPdf() {\n    return fs.copyAsync(sources.pdf_package_path, destinations.pdf_package_path, { filter: filters.pdf });\n  }\n  function copyJs() {\n    return clone(\'https://github.com/coaxial/coverall.git\', destinations.coverall_repo_path);\n  }\n\n\n  return Promise.all([\n      copyLetter(),\n      copyShared(),\n      copyResume(),\n      copyPdf(),\n      copyJs()\n  ]);\n};\n\nArchive.prototype._writeArchive = function() {\n  var self = this;\n  var archive_dir_path = path.resolve(self.output_path, \'..\');\n  var tarPromise = function() {\n    return new Promise(function(resolve, reject) {\n      tar.pack(self.files_path)\n        .pipe(gzip)\n        .pipe(fs.createWriteStream(self.output_path))\n        .on(\'error\', reject)\n        .on(\'finish\', resolve);\n    });\n  };\n\n  return fs.ensureDirAsync(archive_dir_path)\n    .then(tarPromise);\n};\n\nArchive.prototype._delTmpDir = function() {\n  var self = this;\n\n  return fs.removeAsync(self.tmp_path);\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

我正在测试它:

\n\n
/*eslint-env mocha */\nvar chai = require(\'chai\');\nvar chaiAsPromised = require("chai-as-promised");\nvar expect = chai.expect;\nvar Promise = require(\'bluebird\');\nPromise.longStackTraces();\nvar Archive = require(\'../lib/archive\');\nvar path = require(\'path\');\nvar fs = Promise.promisifyAll(require(\'fs-extra\'));\nvar globAsync = Promise.promisify(require(\'glob\'));\nvar tar = require(\'tar-fs\');\nvar zlib = Promise.promisifyAll(require(\'zlib\'));\nvar _ = require(\'lodash\');\n\nchai.use(chaiAsPromised);\n\ndescribe.only(\'Archive\', function() {\n  var pkg;\n\n  beforeEach(function() {\n    pkg = {\n      name: \'test_0790feebb1\',\n      recipient_name: \'Test\',\n      files: {\n        letter: \'../coverall_documents/coverletters/test/letter.tex\',\n        resume: \'../coverall_documents/resume/resume.tex\'\n      },\n      compiled_files: {\n        package: \'../coverall_documents/coverletters/test/test.pdf\'\n      }\n    };\n  });\n\n  // after(function() {\n  //   return Promise.all([\n  //       \'archives/test*\',\n  //       \'test/.tmp\'\n  //   ].map(function(glob_pattern) {\n  //     return globAsync(glob_pattern)\n  //       .each(function(filename) {\n  //         // make every file writeable so the git packfiles can be removed\n  //         return fs.chmodAsync(filename, \'755\')\n  //           .then(function() { fs.removeAsync(filename); });\n  //       })\n  //   }));\n  // });\n\n  describe(\'#make\', function() {\n    it(\'creates an archive\', function() {\n      var modified_pkg = _.cloneDeep(pkg);\n      modified_pkg.name = \'test_0000000001\';\n      var archive_location = path.resolve(\'archives\', modified_pkg.name + \'.tar.gz\');\n      var test_archive = new Archive(modified_pkg);\n\n      return test_archive.make()\n        .then(function() { return fs.statAsync(archive_location); })\n        .then(function(file) { return expect(file).to.exist; })\n        .catch(function(e) { return expect(e).to.not.exist; });\n    });\n\n    it(\'creates a gzip compressed archive\', function() {\n      var modified_pkg = _.cloneDeep(pkg);\n      modified_pkg.name = \'test_0000000002\';\n      var archive_location = path.resolve(\'archives\', modified_pkg.name + \'.tar.gz\');\n      var test_archive = new Archive(modified_pkg);\n\n      // inspired from https://github.com/mafintosh/gunzip-maybe/blob/master/index.js#L6-L11\n      var isGzipped = function(data) {\n        var GZIP_MAGIC_BYTES = [0x1f, 0x8b];\n        var DEFLATE_COMPRESSION_METHOD = 0x08;\n        var buffer = data[1];\n\n        if (buffer[0] !== GZIP_MAGIC_BYTES[0] && buffer[1] !== GZIP_MAGIC_BYTES[1]) return false;\n        if (buffer[2] !== DEFLATE_COMPRESSION_METHOD) return false;\n        return true;\n      };\n\n      return test_archive.make()\n        .then(function() { return fs.openAsync(archive_location, \'r\'); })\n        .then(function(fd) { \n          var buffer = new Buffer(10);\n          var buffer_offset = 0;\n          var buffer_length = 10;\n          var file_position = 0;\n          return fs.readAsync(fd, buffer, buffer_offset, buffer_length, file_position);\n        })\n      .then(function(data) { console.log(\'data\', data); return data; })\n        .then(function(data) { return expect(isGzipped(data)).to.be.true; })\n    });\n\n    it(\'has the correct directory structure\', function() {\n      var modified_pkg = _.cloneDeep(pkg);\n      modified_pkg.name = \'test_0000000003\';\n      var archive_location = path.resolve(\'archives\', modified_pkg.name + \'.tar.gz\');\n      var test_archive = new Archive(modified_pkg);\n      var tmp_extract_path = path.resolve(\'test/.tmp\');\n\n      var tarPromise = function(archive_path) {\n        return new Promise(function(resolve, reject) {\n          fs.createReadStream(archive_path)\n            .pipe(zlib.Unzip())\n            .pipe(tar.extract(tmp_extract_path))\n            .on(\'error\', reject)\n            .on(\'finish\', resolve);\n        })\n      };\n\n      var verifyDir = function() {\n        return Promise.all([\n            \'code\',\n            \'pdf\',\n            \'code/coverall\',\n            \'code/coverall_documents\',\n            \'code/coverall_documents/coverletters\',\n            \'code/coverall_documents/coverletters/test\',\n            \'code/coverall_documents/coverletters/shared\',\n            \'code/coverall_documents/resume\',\n            \'code/coverall_documents/coverletters\'\n        ].map(function(subpath) {\n          return expect(fs.statAsync(path.resolve(tmp_extract_path, subpath)))\n            .to.be.fulfilled;\n        }))\n      };\n\n      return test_archive.make()\n        .then(function() { return tarPromise(archive_location); })\n        .then(function() { return verifyDir(); });\n    });\n\n    it(\'removes the temporary dir\', function() {\n      var modified_pkg = _.cloneDeep(pkg);\n      modified_pkg.name = \'test_0000000004\';\n      var archive_location = path.resolve(\'archives\', modified_pkg.name + \'.tar.gz\');\n      var test_archive = new Archive(modified_pkg);\n      var tmp_dir = path.resolve(\'.tmp\');\n\n      return test_archive.make()\n        .then(function() { return expect(fs.statAsync(tmp_dir)).to.be.rejected; });\n    });\n  });\n});\n
Run Code Online (Sandbox Code Playgroud)\n\n

结果是:

\n\n
$ mocha test\n\n\n  Archive\n    #make\n      \xe2\x9c\x93 creates an archive (644ms)\n      1) creates a gzip compressed archive\n      2) has the correct directory structure\n      3) removes the temporary dir\n\n\n  1 passing (2s)\n  3 failing\n\n  1) Archive #make creates a gzip compressed archive:\n     Uncaught Error: write after end\n      at writeAfterEnd (_stream_writable.js:167:12)\n      at Gzip.Writable.write (_stream_writable.js:214:5)\n      at ondata (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:574:20)\n      at readableAddChunk (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:198:16)\n      at Readable.push (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:162:10)\n      at Pack._encode (node_modules/tar-fs/node_modules/tar-stream/pack.js:154:17)\n      at Pack.entry (node_modules/tar-fs/node_modules/tar-stream/pack.js:100:10)\n      at onstat (node_modules/tar-fs/index.js:108:19)\n      at node_modules/tar-fs/index.js:40:9\n      at FSReqWrap.oncomplete (fs.js:95:15)\n\n  2) Archive #make has the correct directory structure:\n     AssertionError: expected false to be true\n      at Context.<anonymous> (test/archive_spec.js:96:10)\n\n  3) Archive #make removes the temporary dir:\n     Uncaught Error: write after end\n      at writeAfterEnd (_stream_writable.js:167:12)\n      at Gzip.Writable.write (_stream_writable.js:214:5)\n      at ondata (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:574:20)\n      at readableAddChunk (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:198:16)\n      at Readable.push (node_modules/tar-fs/node_modules/tar-stream/node_modules/readable-stream/lib/_stream_readable.js:162:10)\n      at Pack._encode (node_modules/tar-fs/node_modules/tar-stream/pack.js:154:17)\n      at Pack.entry (node_modules/tar-fs/node_modules/tar-stream/pack.js:100:10)\n      at onstat (node_modules/tar-fs/index.js:108:19)\n      at node_modules/tar-fs/index.js:40:9\n      at FSReqWrap.oncomplete (fs.js:95:15)\n
Run Code Online (Sandbox Code Playgroud)\n\n

我怀疑存在竞争条件,因此我注释掉了该after块,看看它是否会产生任何影响,但事实并非如此。

\n\n

我不明白它的Uncaught Error: write after end含义,也不明白为什么堆栈跟踪不可用,即使我正在使用Promise.longStackTraces(). 是什么导致了这个错误?

\n\n

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

\n

msc*_*dex 5

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

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