integration.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. 'use strict'
  2. process.setMaxListeners(1000000);
  3. const fs = require('fs')
  4. const path = require('path')
  5. const {test} = require('tap')
  6. const rimraf = require('rimraf')
  7. const requireInject = require('require-inject')
  8. const workdir = path.join(__dirname, path.basename(__filename, '.js'))
  9. let testfiles = 0
  10. function tmpFile () {
  11. return path.join(workdir, 'test-' + (++testfiles))
  12. }
  13. function readFile (path) {
  14. return fs.readFileSync(path).toString()
  15. }
  16. function didWriteFileAtomic (t, expected, filename, data, options, callback) {
  17. if (options instanceof Function) {
  18. callback = options
  19. options = null
  20. }
  21. if (!options) options = {}
  22. const actual = {}
  23. const {writeFile: writeFileAtomic} = requireInject('../dist', {
  24. fs: Object.assign({}, fs, {
  25. chown (filename, uid, gid, cb) {
  26. actual.uid = uid
  27. actual.gid = gid
  28. process.nextTick(cb)
  29. },
  30. stat (filename, cb) {
  31. fs.stat(filename, (err, stats) => {
  32. if (err) return cb(err)
  33. cb(null, Object.assign(stats, expected || {}))
  34. })
  35. }
  36. })
  37. })
  38. return writeFileAtomic(filename, data, options, err => {
  39. t.isDeeply(actual, expected, 'ownership is as expected')
  40. callback(err)
  41. })
  42. }
  43. function didWriteFileAtomicSync (t, expected, filename, data, options) {
  44. const actual = {}
  45. const {writeFileSync} = requireInject('../dist', {
  46. fs: Object.assign({}, fs, {
  47. chownSync (filename, uid, gid) {
  48. actual.uid = uid
  49. actual.gid = gid
  50. },
  51. statSync (filename) {
  52. const stats = fs.statSync(filename)
  53. return Object.assign(stats, expected || {})
  54. }
  55. })
  56. })
  57. writeFileSync(filename, data, options)
  58. t.isDeeply(actual, expected)
  59. }
  60. function currentUser () {
  61. return {
  62. uid: process.getuid(),
  63. gid: process.getgid()
  64. }
  65. }
  66. test('setup', t => {
  67. rimraf.sync(workdir)
  68. fs.mkdirSync(workdir, {recursive: true})
  69. t.done()
  70. })
  71. test('writes simple file (async)', t => {
  72. t.plan(3)
  73. const file = tmpFile()
  74. didWriteFileAtomic(t, {}, file, '42', err => {
  75. t.ifError(err, 'no error')
  76. t.is(readFile(file), '42', 'content ok')
  77. })
  78. })
  79. test('writes simple file with encoding (async)', t => {
  80. t.plan(3)
  81. const file = tmpFile()
  82. didWriteFileAtomic(t, {}, file, 'foo', 'utf16le', err => {
  83. t.ifError(err, 'no error')
  84. t.is(readFile(file), 'f\u0000o\u0000o\u0000', 'content ok')
  85. })
  86. })
  87. test('writes buffers to simple file (async)', t => {
  88. t.plan(3)
  89. const file = tmpFile()
  90. didWriteFileAtomic(t, {}, file, Buffer.from('42'), err => {
  91. t.ifError(err, 'no error')
  92. t.is(readFile(file), '42', 'content ok')
  93. })
  94. })
  95. test('writes undefined to simple file (async)', t => {
  96. t.plan(3)
  97. const file = tmpFile()
  98. didWriteFileAtomic(t, {}, file, undefined, err => {
  99. t.ifError(err, 'no error')
  100. t.is(readFile(file), '', 'content ok')
  101. })
  102. })
  103. test('writes to symlinks without clobbering (async)', t => {
  104. t.plan(5)
  105. const file = tmpFile()
  106. const link = tmpFile()
  107. fs.writeFileSync(file, '42')
  108. fs.symlinkSync(file, link)
  109. didWriteFileAtomic(t, currentUser(), link, '43', err => {
  110. t.ifError(err, 'no error')
  111. t.is(readFile(file), '43', 'target content ok')
  112. t.is(readFile(link), '43', 'link content ok')
  113. t.ok(fs.lstatSync(link).isSymbolicLink(), 'link is link')
  114. })
  115. })
  116. test('runs chown on given file (async)', t => {
  117. const file = tmpFile()
  118. didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '42', { chown: { uid: 42, gid: 43 } }, err => {
  119. t.ifError(err, 'no error')
  120. t.is(readFile(file), '42', 'content ok')
  121. t.done()
  122. })
  123. })
  124. test('writes simple file with no chown (async)', t => {
  125. t.plan(3)
  126. const file = tmpFile()
  127. didWriteFileAtomic(t, {}, file, '42', { chown: false }, err => {
  128. t.ifError(err, 'no error')
  129. t.is(readFile(file), '42', 'content ok')
  130. t.done()
  131. })
  132. })
  133. test('runs chmod on given file (async)', t => {
  134. t.plan(5)
  135. const file = tmpFile()
  136. didWriteFileAtomic(t, {}, file, '42', { mode: parseInt('741', 8) }, err => {
  137. t.ifError(err, 'no error')
  138. const stat = fs.statSync(file)
  139. t.is(stat.mode, parseInt('100741', 8))
  140. didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '23', { chown: { uid: 42, gid: 43 } }, err => {
  141. t.ifError(err, 'no error')
  142. })
  143. })
  144. })
  145. test('run chmod AND chown (async)', t => {
  146. t.plan(3)
  147. const file = tmpFile()
  148. didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '42', { mode: parseInt('741', 8), chown: { uid: 42, gid: 43 } }, err => {
  149. t.ifError(err, 'no error')
  150. const stat = fs.statSync(file)
  151. t.is(stat.mode, parseInt('100741', 8))
  152. })
  153. })
  154. test('does not change chmod by default (async)', t => {
  155. t.plan(5)
  156. const file = tmpFile()
  157. didWriteFileAtomic(t, {}, file, '42', { mode: parseInt('741', 8) }, err => {
  158. t.ifError(err, 'no error')
  159. didWriteFileAtomic(t, currentUser(), file, '43', err => {
  160. t.ifError(err, 'no error')
  161. const stat = fs.statSync(file)
  162. t.is(stat.mode, parseInt('100741', 8))
  163. })
  164. })
  165. })
  166. test('does not change chown by default (async)', t => {
  167. t.plan(6)
  168. const file = tmpFile()
  169. didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '42', { chown: { uid: 42, gid: 43 } }, _setModeOnly)
  170. function _setModeOnly (err) {
  171. t.ifError(err, 'no error')
  172. didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '43', { mode: parseInt('741', 8) }, _allDefault)
  173. }
  174. function _allDefault (err) {
  175. t.ifError(err, 'no error')
  176. didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '43', _noError)
  177. }
  178. function _noError (err) {
  179. t.ifError(err, 'no error')
  180. }
  181. })
  182. test('writes simple file (sync)', t => {
  183. t.plan(2)
  184. const file = tmpFile()
  185. didWriteFileAtomicSync(t, {}, file, '42')
  186. t.is(readFile(file), '42')
  187. })
  188. test('writes simple file with encoding (sync)', t => {
  189. t.plan(2)
  190. const file = tmpFile()
  191. didWriteFileAtomicSync(t, {}, file, 'foo', 'utf16le')
  192. t.is(readFile(file), 'f\u0000o\u0000o\u0000')
  193. })
  194. test('writes simple buffer file (sync)', t => {
  195. t.plan(2)
  196. const file = tmpFile()
  197. didWriteFileAtomicSync(t, {}, file, Buffer.from('42'))
  198. t.is(readFile(file), '42')
  199. })
  200. test('writes undefined file (sync)', t => {
  201. t.plan(2)
  202. const file = tmpFile()
  203. didWriteFileAtomicSync(t, {}, file, undefined)
  204. t.is(readFile(file), '')
  205. })
  206. test('writes to symlinks without clobbering (sync)', t => {
  207. t.plan(4)
  208. const file = tmpFile()
  209. const link = tmpFile()
  210. fs.writeFileSync(file, '42')
  211. fs.symlinkSync(file, link)
  212. didWriteFileAtomicSync(t, currentUser(), link, '43')
  213. t.is(readFile(file), '43', 'target content ok')
  214. t.is(readFile(link), '43', 'link content ok')
  215. t.ok(fs.lstatSync(link).isSymbolicLink(), 'link is link')
  216. })
  217. test('runs chown on given file (sync)', t => {
  218. t.plan(1)
  219. const file = tmpFile()
  220. didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '42', { chown: { uid: 42, gid: 43 } })
  221. })
  222. test('runs chmod on given file (sync)', t => {
  223. t.plan(3)
  224. const file = tmpFile()
  225. didWriteFileAtomicSync(t, {}, file, '42', { mode: parseInt('741', 8) })
  226. const stat = fs.statSync(file)
  227. t.is(stat.mode, parseInt('100741', 8))
  228. didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '23', { chown: { uid: 42, gid: 43 } })
  229. })
  230. test('runs chown and chmod (sync)', t => {
  231. t.plan(2)
  232. const file = tmpFile()
  233. didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '42', { mode: parseInt('741', 8), chown: { uid: 42, gid: 43 } })
  234. const stat = fs.statSync(file)
  235. t.is(stat.mode, parseInt('100741', 8))
  236. })
  237. test('does not change chmod by default (sync)', t => {
  238. t.plan(3)
  239. const file = tmpFile()
  240. didWriteFileAtomicSync(t, {}, file, '42', { mode: parseInt('741', 8) })
  241. didWriteFileAtomicSync(t, currentUser(), file, '43')
  242. const stat = fs.statSync(file)
  243. t.is(stat.mode, parseInt('100741', 8))
  244. })
  245. test('does not change chown by default (sync)', t => {
  246. t.plan(3)
  247. const file = tmpFile()
  248. didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '42', { chown: { uid: 42, gid: 43 } })
  249. didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '43', { mode: parseInt('741', 8) })
  250. didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '44')
  251. })
  252. test('cleanup', t => {
  253. rimraf.sync(workdir)
  254. t.done()
  255. })