|
1 | 1 | /* eslint-env mocha */
|
2 | 2 | 'use strict'
|
3 | 3 |
|
| 4 | +const { promisify } = require('es6-promisify') |
4 | 5 | const { getDescribe, getIt, expect } = require('../utils/mocha')
|
| 6 | +const { DAGNode, DAGLink } = require('ipld-dag-pb') |
| 7 | + |
| 8 | +const createDAGNode = promisify((data, links, cb) => { |
| 9 | + DAGNode.create(data, links, cb) |
| 10 | +}) |
| 11 | + |
| 12 | +const createDAGLink = promisify((name, size, cid, cb) => { |
| 13 | + DAGLink.create(name, size, cid, cb) |
| 14 | +}) |
| 15 | + |
| 16 | +const addDAGLink = promisify((node, link, cb) => { |
| 17 | + DAGNode.addLink(node, link, cb) |
| 18 | +}) |
5 | 19 |
|
6 | 20 | module.exports = (createCommon, options) => {
|
7 | 21 | const describe = getDescribe(options)
|
@@ -41,5 +55,188 @@ module.exports = (createCommon, options) => {
|
41 | 55 | expect(res).to.exist()
|
42 | 56 | })
|
43 | 57 | })
|
| 58 | + |
| 59 | + it('should clean up unpinned data', async () => { |
| 60 | + // Get initial list of local blocks |
| 61 | + const refsBeforeAdd = await ipfs.refs.local() |
| 62 | + |
| 63 | + // Add some data. Note: this will implicitly pin the data, which causes |
| 64 | + // some blocks to be added for the data itself and for the pinning |
| 65 | + // information that refers to the blocks |
| 66 | + const addRes = await ipfs.add(Buffer.from('apples')) |
| 67 | + const hash = addRes[0].hash |
| 68 | + |
| 69 | + // Get the list of local blocks after the add, should be bigger than |
| 70 | + // the initial list and contain hash |
| 71 | + const refsAfterAdd = await ipfs.refs.local() |
| 72 | + expect(refsAfterAdd.length).to.be.gt(refsBeforeAdd.length) |
| 73 | + expect(refsAfterAdd.map(r => r.ref)).includes(hash) |
| 74 | + |
| 75 | + // Run garbage collection |
| 76 | + await ipfs.repo.gc() |
| 77 | + |
| 78 | + // Get the list of local blocks after GC, should still contain the hash, |
| 79 | + // because the file is still pinned |
| 80 | + const refsAfterGc = await ipfs.refs.local() |
| 81 | + expect(refsAfterGc.map(r => r.ref)).includes(hash) |
| 82 | + |
| 83 | + // Unpin the data |
| 84 | + await ipfs.pin.rm(hash) |
| 85 | + |
| 86 | + // Run garbage collection |
| 87 | + await ipfs.repo.gc() |
| 88 | + |
| 89 | + // The list of local blocks should now be the same as at the start |
| 90 | + const refsAfterUnpinAndGc = await ipfs.refs.local() |
| 91 | + expect(refsAfterUnpinAndGc).to.eql(refsBeforeAdd) |
| 92 | + }) |
| 93 | + |
| 94 | + it('should clean up removed MFS files', async () => { |
| 95 | + // Get initial list of local blocks |
| 96 | + const refsBeforeAdd = await ipfs.refs.local() |
| 97 | + |
| 98 | + // Add a file to MFS |
| 99 | + await ipfs.files.write('/test', Buffer.from('oranges'), { create: true }) |
| 100 | + const stats = await ipfs.files.stat('/test') |
| 101 | + expect(stats.type).to.equal('file') |
| 102 | + const hash = stats.hash |
| 103 | + |
| 104 | + // Get the list of local blocks after the add, should be bigger than |
| 105 | + // the initial list and contain hash |
| 106 | + const refsAfterAdd = await ipfs.refs.local() |
| 107 | + expect(refsAfterAdd.length).to.be.gt(refsBeforeAdd.length) |
| 108 | + expect(refsAfterAdd.map(r => r.ref)).includes(hash) |
| 109 | + |
| 110 | + // Run garbage collection |
| 111 | + await ipfs.repo.gc() |
| 112 | + |
| 113 | + // Get the list of local blocks after GC, should still contain the hash, |
| 114 | + // because the file is in MFS |
| 115 | + const refsAfterGc = await ipfs.refs.local() |
| 116 | + expect(refsAfterGc.map(r => r.ref)).includes(hash) |
| 117 | + |
| 118 | + // Remove the file |
| 119 | + await ipfs.files.rm('/test') |
| 120 | + |
| 121 | + // Run garbage collection |
| 122 | + await ipfs.repo.gc() |
| 123 | + |
| 124 | + // The list of local blocks should now be the same as at the start |
| 125 | + const refsAfterUnpinAndGc = await ipfs.refs.local() |
| 126 | + expect(refsAfterUnpinAndGc).to.eql(refsBeforeAdd) |
| 127 | + }) |
| 128 | + |
| 129 | + it('should clean up block only after unpinned and removed from MFS', async () => { |
| 130 | + // Get initial list of local blocks |
| 131 | + const refsBeforeAdd = await ipfs.refs.local() |
| 132 | + |
| 133 | + // Add a file to MFS |
| 134 | + await ipfs.files.write('/test', Buffer.from('peaches'), { create: true }) |
| 135 | + const stats = await ipfs.files.stat('/test') |
| 136 | + expect(stats.type).to.equal('file') |
| 137 | + const mfsFileHash = stats.hash |
| 138 | + |
| 139 | + // Get the CID of the data in the file |
| 140 | + const block = await ipfs.block.get(mfsFileHash) |
| 141 | + |
| 142 | + // Add the data to IPFS (which implicitly pins the data) |
| 143 | + const addRes = await ipfs.add(block.data) |
| 144 | + const dataHash = addRes[0].hash |
| 145 | + |
| 146 | + // Get the list of local blocks after the add, should be bigger than |
| 147 | + // the initial list and contain the data hash |
| 148 | + const refsAfterAdd = await ipfs.refs.local() |
| 149 | + expect(refsAfterAdd.length).to.be.gt(refsBeforeAdd.length) |
| 150 | + const hashesAfterAdd = refsAfterAdd.map(r => r.ref) |
| 151 | + expect(hashesAfterAdd).includes(dataHash) |
| 152 | + |
| 153 | + // Run garbage collection |
| 154 | + await ipfs.repo.gc() |
| 155 | + |
| 156 | + // Get the list of local blocks after GC, should still contain the hash, |
| 157 | + // because the file is pinned and in MFS |
| 158 | + const refsAfterGc = await ipfs.refs.local() |
| 159 | + const hashesAfterGc = refsAfterGc.map(r => r.ref) |
| 160 | + expect(hashesAfterGc).includes(dataHash) |
| 161 | + |
| 162 | + // Remove the file |
| 163 | + await ipfs.files.rm('/test') |
| 164 | + |
| 165 | + // Run garbage collection |
| 166 | + await ipfs.repo.gc() |
| 167 | + |
| 168 | + // Get the list of local blocks after GC, should still contain the hash, |
| 169 | + // because the file is still pinned |
| 170 | + const refsAfterRmAndGc = await ipfs.refs.local() |
| 171 | + const hashesAfterRmAndGc = refsAfterRmAndGc.map(r => r.ref) |
| 172 | + expect(hashesAfterRmAndGc).not.includes(mfsFileHash) |
| 173 | + expect(hashesAfterRmAndGc).includes(dataHash) |
| 174 | + |
| 175 | + // Unpin the data |
| 176 | + await ipfs.pin.rm(dataHash) |
| 177 | + |
| 178 | + // Run garbage collection |
| 179 | + await ipfs.repo.gc() |
| 180 | + |
| 181 | + // The list of local blocks should now be the same as at the start |
| 182 | + const refsAfterUnpinAndGc = await ipfs.refs.local() |
| 183 | + expect(refsAfterUnpinAndGc).to.eql(refsBeforeAdd) |
| 184 | + }) |
| 185 | + |
| 186 | + it('should clean up indirectly pinned data after recursive pin removal', async () => { |
| 187 | + // Get initial list of local blocks |
| 188 | + const refsBeforeAdd = await ipfs.refs.local() |
| 189 | + |
| 190 | + // Add some data |
| 191 | + const addRes = await ipfs.add(Buffer.from('pears')) |
| 192 | + const dataHash = addRes[0].hash |
| 193 | + |
| 194 | + // Unpin the data |
| 195 | + await ipfs.pin.rm(dataHash) |
| 196 | + |
| 197 | + // Create a link to the data from an object |
| 198 | + const link = await createDAGLink('p', addRes[0].size, dataHash) |
| 199 | + const node = await createDAGNode(Buffer.from('fruit')) |
| 200 | + const obj = await addDAGLink(node, link) |
| 201 | + |
| 202 | + // Put the object into IPFS |
| 203 | + const objHash = (await ipfs.object.put(obj)).toString() |
| 204 | + |
| 205 | + // Putting an object doesn't pin it |
| 206 | + expect((await ipfs.pin.ls()).map(p => p.hash)).not.includes(objHash) |
| 207 | + |
| 208 | + // Get the list of local blocks after the add, should be bigger than |
| 209 | + // the initial list and contain data and object hash |
| 210 | + const refsAfterAdd = await ipfs.refs.local() |
| 211 | + expect(refsAfterAdd.length).to.be.gt(refsBeforeAdd.length) |
| 212 | + const hashesAfterAdd = refsAfterAdd.map(r => r.ref) |
| 213 | + expect(hashesAfterAdd).includes(objHash) |
| 214 | + expect(hashesAfterAdd).includes(dataHash) |
| 215 | + |
| 216 | + // Recursively pin the object |
| 217 | + await ipfs.pin.add(objHash, { recursive: true }) |
| 218 | + |
| 219 | + // The data should now be indirectly pinned |
| 220 | + const pins = await ipfs.pin.ls() |
| 221 | + expect(pins.find(p => p.hash === dataHash).type).to.eql('indirect') |
| 222 | + |
| 223 | + // Run garbage collection |
| 224 | + await ipfs.repo.gc() |
| 225 | + |
| 226 | + // Get the list of local blocks after GC, should still contain the data |
| 227 | + // hash, because the data is still (indirectly) pinned |
| 228 | + const refsAfterGc = await ipfs.refs.local() |
| 229 | + expect(refsAfterGc.map(r => r.ref)).includes(dataHash) |
| 230 | + |
| 231 | + // Recursively unpin the object |
| 232 | + await ipfs.pin.rm(objHash) |
| 233 | + |
| 234 | + // Run garbage collection |
| 235 | + await ipfs.repo.gc() |
| 236 | + |
| 237 | + // The list of local blocks should now be the same as at the start |
| 238 | + const refsAfterUnpinAndGc = await ipfs.refs.local() |
| 239 | + expect(refsAfterUnpinAndGc).to.eql(refsBeforeAdd) |
| 240 | + }) |
44 | 241 | })
|
45 | 242 | }
|
0 commit comments