RollingFileStream-test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. require("should");
  2. const fs = require("fs-extra"),
  3. path = require("path"),
  4. util = require("util"),
  5. zlib = require("zlib"),
  6. streams = require("stream"),
  7. RollingFileStream = require("../lib").RollingFileStream;
  8. const gunzip = util.promisify(zlib.gunzip);
  9. const fullPath = f => path.join(__dirname, f);
  10. const remove = filename => fs.unlink(fullPath(filename)).catch(() => {});
  11. const create = filename => fs.writeFile(fullPath(filename), "test file");
  12. const write = (stream, data) => {
  13. return new Promise((resolve, reject) => {
  14. stream.write(data, "utf8", e => {
  15. if (e) {
  16. reject(e);
  17. } else {
  18. resolve();
  19. }
  20. });
  21. });
  22. };
  23. const writeInSequence = async (stream, messages) => {
  24. for (let i = 0; i < messages.length; i += 1) {
  25. await write(stream, messages[i] + "\n");
  26. }
  27. return new Promise(resolve => {
  28. stream.end(resolve);
  29. });
  30. };
  31. const close = async (stream) => new Promise(
  32. (resolve, reject) => stream.end(e => e ? reject(e) : resolve())
  33. );
  34. describe("RollingFileStream", function() {
  35. describe("arguments", function() {
  36. let stream;
  37. before(async function() {
  38. await remove("test-rolling-file-stream");
  39. stream = new RollingFileStream(
  40. path.join(__dirname, "test-rolling-file-stream"),
  41. 1024,
  42. 5
  43. );
  44. });
  45. after(async function() {
  46. await close(stream);
  47. await remove("test-rolling-file-stream");
  48. });
  49. it("should take a filename, file size (bytes), no. backups, return Writable", function() {
  50. stream.should.be.an.instanceOf(streams.Writable);
  51. stream.filename.should.eql(
  52. path.join(__dirname, "test-rolling-file-stream")
  53. );
  54. stream.size.should.eql(1024);
  55. stream.backups.should.eql(5);
  56. });
  57. it("should apply default settings to the underlying stream", function() {
  58. stream.theStream.mode.should.eql(420);
  59. stream.theStream.flags.should.eql("a");
  60. });
  61. });
  62. describe("with stream arguments", function() {
  63. let stream;
  64. it("should pass them to the underlying stream", function() {
  65. stream = new RollingFileStream(
  66. path.join(__dirname, "test-rolling-file-stream"),
  67. 1024,
  68. 5,
  69. { mode: parseInt("0666", 8) }
  70. );
  71. stream.theStream.mode.should.eql(parseInt("0666", 8));
  72. });
  73. after(async function() {
  74. await close(stream);
  75. await remove("test-rolling-file-stream");
  76. });
  77. });
  78. describe("without size", function() {
  79. let stream;
  80. it("should default to max int size", function() {
  81. stream = new RollingFileStream(
  82. path.join(__dirname, "test-rolling-file-stream")
  83. );
  84. stream.size.should.eql(Number.MAX_SAFE_INTEGER);
  85. });
  86. after(async function() {
  87. await close(stream);
  88. await remove("test-rolling-file-stream");
  89. });
  90. });
  91. describe("without number of backups", function() {
  92. let stream;
  93. it("should default to 1 backup", function() {
  94. stream = new RollingFileStream(
  95. path.join(__dirname, "test-rolling-file-stream"),
  96. 1024
  97. );
  98. stream.backups.should.eql(1);
  99. });
  100. after(async function() {
  101. await close(stream);
  102. await remove("test-rolling-file-stream");
  103. });
  104. });
  105. describe("writing less than the file size", function() {
  106. before(async function() {
  107. await remove("test-rolling-file-stream-write-less");
  108. const stream = new RollingFileStream(
  109. path.join(__dirname, "test-rolling-file-stream-write-less"),
  110. 100
  111. );
  112. await writeInSequence(stream, ["cheese"]);
  113. });
  114. after(async function() {
  115. await remove("test-rolling-file-stream-write-less");
  116. });
  117. it("should write to the file", async function() {
  118. const contents = await fs.readFile(
  119. path.join(__dirname, "test-rolling-file-stream-write-less"),
  120. "utf8"
  121. );
  122. contents.should.eql("cheese\n");
  123. });
  124. it("should write one file", async function() {
  125. const files = await fs.readdir(__dirname);
  126. files
  127. .filter(
  128. file => file.indexOf("test-rolling-file-stream-write-less") > -1
  129. )
  130. .should.have.length(1);
  131. });
  132. });
  133. describe("writing more than the file size", function() {
  134. before(async function() {
  135. await remove("test-rolling-file-stream-write-more");
  136. await remove("test-rolling-file-stream-write-more.1");
  137. const stream = new RollingFileStream(
  138. path.join(__dirname, "test-rolling-file-stream-write-more"),
  139. 45
  140. );
  141. await writeInSequence(
  142. stream,
  143. [0, 1, 2, 3, 4, 5, 6].map(i => i + ".cheese")
  144. );
  145. });
  146. after(async function() {
  147. await remove("test-rolling-file-stream-write-more");
  148. await remove("test-rolling-file-stream-write-more.1");
  149. });
  150. it("should write two files", async function() {
  151. const files = await fs.readdir(__dirname);
  152. files
  153. .filter(
  154. file => file.indexOf("test-rolling-file-stream-write-more") > -1
  155. )
  156. .should.have.length(2);
  157. });
  158. it("should write the last two log messages to the first file", async function() {
  159. const contents = await fs.readFile(
  160. path.join(__dirname, "test-rolling-file-stream-write-more"),
  161. "utf8"
  162. );
  163. contents.should.eql("5.cheese\n6.cheese\n");
  164. });
  165. it("should write the first five log messages to the second file", async function() {
  166. const contents = await fs.readFile(
  167. path.join(__dirname, "test-rolling-file-stream-write-more.1"),
  168. "utf8"
  169. );
  170. contents.should.eql("0.cheese\n1.cheese\n2.cheese\n3.cheese\n4.cheese\n");
  171. });
  172. });
  173. describe("with options.compress = true", function() {
  174. before(async function() {
  175. const stream = new RollingFileStream(
  176. path.join(__dirname, "compressed-backups.log"),
  177. 30, //30 bytes max size
  178. 2, //two backup files to keep
  179. { compress: true }
  180. );
  181. const messages = [
  182. "This is the first log message.",
  183. "This is the second log message.",
  184. "This is the third log message.",
  185. "This is the fourth log message."
  186. ];
  187. await writeInSequence(stream, messages);
  188. });
  189. it("should produce three files, with the backups compressed", async function() {
  190. const files = await fs.readdir(__dirname);
  191. const testFiles = files
  192. .filter(f => f.indexOf("compressed-backups.log") > -1)
  193. .sort();
  194. testFiles.length.should.eql(3);
  195. testFiles.should.eql([
  196. "compressed-backups.log",
  197. "compressed-backups.log.1.gz",
  198. "compressed-backups.log.2.gz"
  199. ]);
  200. let contents = await fs.readFile(
  201. path.join(__dirname, testFiles[0]),
  202. "utf8"
  203. );
  204. contents.should.eql("This is the fourth log message.\n");
  205. let gzipped = await fs.readFile(path.join(__dirname, testFiles[1]));
  206. contents = await gunzip(gzipped);
  207. contents.toString("utf8").should.eql("This is the third log message.\n");
  208. gzipped = await fs.readFile(path.join(__dirname, testFiles[2]));
  209. contents = await gunzip(gzipped);
  210. contents.toString("utf8").should.eql("This is the second log message.\n");
  211. });
  212. after(function() {
  213. return Promise.all([
  214. remove("compressed-backups.log"),
  215. remove("compressed-backups.log.1.gz"),
  216. remove("compressed-backups.log.2.gz")
  217. ]);
  218. });
  219. });
  220. describe("with options.keepFileExt = true", function() {
  221. before(async function() {
  222. const stream = new RollingFileStream(
  223. path.join(__dirname, "extKept-backups.log"),
  224. 30, //30 bytes max size
  225. 2, //two backup files to keep
  226. { keepFileExt: true }
  227. );
  228. const messages = [
  229. "This is the first log message.",
  230. "This is the second log message.",
  231. "This is the third log message.",
  232. "This is the fourth log message."
  233. ];
  234. await writeInSequence(stream, messages);
  235. });
  236. it("should produce three files, with the file-extension kept", async function() {
  237. const files = await fs.readdir(__dirname);
  238. const testFiles = files
  239. .filter(f => f.indexOf("extKept-backups") > -1)
  240. .sort();
  241. testFiles.length.should.eql(3);
  242. testFiles.should.eql([
  243. "extKept-backups.1.log",
  244. "extKept-backups.2.log",
  245. "extKept-backups.log"
  246. ]);
  247. let contents = await fs.readFile(
  248. path.join(__dirname, testFiles[0]),
  249. "utf8"
  250. );
  251. contents.should.eql("This is the third log message.\n");
  252. contents = await fs.readFile(path.join(__dirname, testFiles[1]), "utf8");
  253. contents.toString("utf8").should.eql("This is the second log message.\n");
  254. contents = await fs.readFile(path.join(__dirname, testFiles[2]), "utf8");
  255. contents.toString("utf8").should.eql("This is the fourth log message.\n");
  256. });
  257. after(function() {
  258. return Promise.all([
  259. remove("extKept-backups.log"),
  260. remove("extKept-backups.1.log"),
  261. remove("extKept-backups.2.log")
  262. ]);
  263. });
  264. });
  265. describe("with options.compress = true and keepFileExt = true", function() {
  266. before(async function() {
  267. const stream = new RollingFileStream(
  268. path.join(__dirname, "compressed-backups.log"),
  269. 30, //30 bytes max size
  270. 2, //two backup files to keep
  271. { compress: true, keepFileExt: true }
  272. );
  273. const messages = [
  274. "This is the first log message.",
  275. "This is the second log message.",
  276. "This is the third log message.",
  277. "This is the fourth log message."
  278. ];
  279. await writeInSequence(stream, messages);
  280. });
  281. it("should produce three files, with the backups compressed", async function() {
  282. const files = await fs.readdir(__dirname);
  283. const testFiles = files
  284. .filter(f => f.indexOf("compressed-backups") > -1)
  285. .sort();
  286. testFiles.length.should.eql(3);
  287. testFiles.should.eql([
  288. "compressed-backups.1.log.gz",
  289. "compressed-backups.2.log.gz",
  290. "compressed-backups.log"
  291. ]);
  292. let contents = await fs.readFile(
  293. path.join(__dirname, testFiles[2]),
  294. "utf8"
  295. );
  296. contents.should.eql("This is the fourth log message.\n");
  297. let gzipped = await fs.readFile(path.join(__dirname, testFiles[1]));
  298. contents = await gunzip(gzipped);
  299. contents.toString("utf8").should.eql("This is the second log message.\n");
  300. gzipped = await fs.readFile(path.join(__dirname, testFiles[0]));
  301. contents = await gunzip(gzipped);
  302. contents.toString("utf8").should.eql("This is the third log message.\n");
  303. });
  304. after(function() {
  305. return Promise.all([
  306. remove("compressed-backups.log"),
  307. remove("compressed-backups.1.log.gz"),
  308. remove("compressed-backups.2.log.gz")
  309. ]);
  310. });
  311. });
  312. describe("when many files already exist", function() {
  313. before(async function() {
  314. await Promise.all([
  315. remove("test-rolling-stream-with-existing-files.11"),
  316. remove("test-rolling-stream-with-existing-files.20"),
  317. remove("test-rolling-stream-with-existing-files.-1"),
  318. remove("test-rolling-stream-with-existing-files.1.1"),
  319. remove("test-rolling-stream-with-existing-files.1")
  320. ]);
  321. await Promise.all([
  322. create("test-rolling-stream-with-existing-files.11"),
  323. create("test-rolling-stream-with-existing-files.20"),
  324. create("test-rolling-stream-with-existing-files.-1"),
  325. create("test-rolling-stream-with-existing-files.1.1"),
  326. create("test-rolling-stream-with-existing-files.1")
  327. ]);
  328. const stream = new RollingFileStream(
  329. path.join(__dirname, "test-rolling-stream-with-existing-files"),
  330. 18,
  331. 5
  332. );
  333. await writeInSequence(
  334. stream,
  335. [0, 1, 2, 3, 4, 5, 6].map(i => i + ".cheese")
  336. );
  337. });
  338. after(function() {
  339. return Promise.all(
  340. [
  341. "test-rolling-stream-with-existing-files.-1",
  342. "test-rolling-stream-with-existing-files",
  343. "test-rolling-stream-with-existing-files.1.1",
  344. "test-rolling-stream-with-existing-files.0",
  345. "test-rolling-stream-with-existing-files.1",
  346. "test-rolling-stream-with-existing-files.2",
  347. "test-rolling-stream-with-existing-files.3",
  348. "test-rolling-stream-with-existing-files.4",
  349. "test-rolling-stream-with-existing-files.5",
  350. "test-rolling-stream-with-existing-files.6",
  351. "test-rolling-stream-with-existing-files.11",
  352. "test-rolling-stream-with-existing-files.20"
  353. ].map(remove)
  354. );
  355. });
  356. it("should roll the files, removing the highest indices", async function() {
  357. const files = await fs.readdir(__dirname);
  358. files.should.containEql("test-rolling-stream-with-existing-files");
  359. files.should.containEql("test-rolling-stream-with-existing-files.1");
  360. files.should.containEql("test-rolling-stream-with-existing-files.2");
  361. files.should.containEql("test-rolling-stream-with-existing-files.3");
  362. files.should.containEql("test-rolling-stream-with-existing-files.4");
  363. });
  364. });
  365. // in windows, you can't delete a directory if there is an open file handle
  366. if (process.platform !== "win32") {
  367. describe("when the directory gets deleted", function() {
  368. var stream;
  369. before(function(done) {
  370. stream = new RollingFileStream(
  371. path.join("subdir", "test-rolling-file-stream"),
  372. 5,
  373. 5
  374. );
  375. stream.write("initial", "utf8", done);
  376. });
  377. after(async () => {
  378. await fs.unlink(path.join("subdir", "test-rolling-file-stream"));
  379. await fs.rmdir("subdir");
  380. });
  381. it("handles directory deletion gracefully", async function() {
  382. stream.theStream.on("error", e => {
  383. throw e;
  384. });
  385. await fs.unlink(path.join("subdir", "test-rolling-file-stream"));
  386. await fs.rmdir("subdir");
  387. await new Promise(resolve => stream.write("rollover", "utf8", resolve));
  388. await close(stream);
  389. (await fs.readFile(
  390. path.join("subdir", "test-rolling-file-stream"),
  391. "utf8"
  392. )).should.eql("rollover");
  393. });
  394. });
  395. }
  396. });