Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.

Commit d89fd06

Browse files
JonKronedaviddias
authored andcommitted
test: Pin API interop (#19)
* feat: pin interop tests This is a rough draft of a suite of tests to verify that the go and js ipfs implementations behave the same when adding/removing pins to the datastore. I've found that they produce essentially the same pinsets. There are a few secondary differences I've noticed such as: fresh go repos include the readme files as pinned while the js impl does not. Draft #2 coming up! Lots of code to dedupe * docs: update instructions to test against local go-ipfs version * Update package.json * chore: update ipfs
1 parent 55921a5 commit d89fd06

File tree

6 files changed

+258
-4
lines changed

6 files changed

+258
-4
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ This repository will be used for interop tests. Please jump into the issues if y
4444

4545
```
4646
> Do the steps in the install section, then
47-
> IPFS_EXEC=<path to the go-ipfs version you want to try> npm test
47+
> IPFS_GO_EXEC=<path to the go-ipfs version you want to try> npm test
4848
```
4949

5050
## Contribute

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@
5252
"eslint-plugin-react": "^7.8.2",
5353
"expose-loader": "^0.7.5",
5454
"form-data": "^2.3.2",
55-
"go-ipfs-dep": "^0.4.15",
55+
"go-ipfs-dep": "~0.4.15",
5656
"hat": "0.0.3",
57-
"ipfs": "~0.29.0",
57+
"ipfs": "~0.30.0",
5858
"ipfs-api": "^22.0.0",
5959
"ipfsd-ctl": "~0.37.0",
6060
"left-pad": "^1.3.0",
@@ -68,6 +68,5 @@
6868
"stream-to-promise": "^2.2.0",
6969
"transform-loader": "^0.2.4"
7070
},
71-
"dependencies": {},
7271
"contributors": []
7372
}
1.94 MB
Loading

test/node.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ require('./repo')
66
require('./exchange-files')
77
require('./kad-dht')
88
require('./pubsub')
9+
require('./pin')

test/pin.js

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const fs = require('fs')
5+
const chai = require('chai')
6+
const dirtyChai = require('dirty-chai')
7+
const expect = chai.expect
8+
chai.use(dirtyChai)
9+
10+
const DaemonFactory = require('ipfsd-ctl')
11+
12+
const utils = require('./utils/pin-utils')
13+
14+
describe('pin', function () {
15+
this.timeout(5 * 1000)
16+
17+
const filePath = 'test/fixtures/planets/jupiter-from-cassini.jpg'
18+
const jupiter = [{
19+
path: filePath,
20+
content: fs.readFileSync(filePath)
21+
}]
22+
23+
let daemons = []
24+
function spawnAndStart (type, repoPath = utils.tmpPath()) {
25+
return new Promise((resolve, reject) => {
26+
DaemonFactory.create({ type })
27+
.spawn({
28+
repoPath,
29+
disposable: false
30+
}, (err, daemon) => {
31+
if (err) return reject(err)
32+
daemons.push(daemon)
33+
34+
if (daemon.initialized) {
35+
// repo already exists, no need to init
36+
daemon.start(err => err ? reject(err) : resolve(daemon))
37+
} else {
38+
daemon.init((err, initRes) => {
39+
if (err) return reject(err)
40+
daemon.start(err => err ? reject(err) : resolve(daemon))
41+
})
42+
}
43+
})
44+
})
45+
}
46+
47+
function withDaemons (pipeline) {
48+
return Promise.all([
49+
spawnAndStart('go').then(utils.removeAllPins).then(pipeline),
50+
spawnAndStart('js').then(utils.removeAllPins).then(pipeline)
51+
])
52+
}
53+
54+
afterEach(function () {
55+
this.timeout(25 * 1000)
56+
return utils.stopDaemons(daemons)
57+
.then(() => { daemons = [] })
58+
})
59+
60+
describe('pin add', function () {
61+
// Pinning a large file recursively results in the same pins
62+
it('pin recursively', function () {
63+
this.timeout(30 * 1000)
64+
this.slow(30 * 1000)
65+
66+
function pipeline (daemon) {
67+
return daemon.api.add(jupiter, { pin: false })
68+
.then(chunks => daemon.api.pin.add(chunks[0].hash))
69+
.then(() => daemon.api.pin.ls())
70+
}
71+
72+
return withDaemons(pipeline)
73+
.then(([goPins, jsPins]) => {
74+
expect(goPins.length).to.be.gt(0)
75+
expect(jsPins).to.deep.include.members(goPins)
76+
expect(goPins).to.deep.include.members(jsPins)
77+
})
78+
})
79+
80+
// Pinning a large file with recursive=false results in the same direct pin
81+
it('pin directly', function () {
82+
this.timeout(30 * 1000)
83+
this.slow(20 * 1000)
84+
85+
function pipeline (daemon) {
86+
return daemon.api.add(jupiter, { pin: false })
87+
.then(chunks => daemon.api.pin.add(chunks[0].hash, { recursive: false }))
88+
.then(() => daemon.api.pin.ls())
89+
}
90+
91+
return withDaemons(pipeline)
92+
.then(([goPins, jsPins]) => {
93+
expect(goPins.length).to.be.gt(0)
94+
expect(jsPins).to.deep.include.members(goPins)
95+
expect(goPins).to.deep.include.members(jsPins)
96+
})
97+
})
98+
})
99+
100+
describe('pin rm', function () {
101+
// removing a root pin removes children as long as they're
102+
// not part of another pin's dag
103+
it('pin recursively, remove the root pin', function () {
104+
this.timeout(20 * 1000)
105+
this.slow(20 * 1000)
106+
107+
function pipeline (daemon) {
108+
return daemon.api.add(jupiter)
109+
.then(chunks => {
110+
const testFolder = chunks.find(chunk => chunk.path === 'test')
111+
return daemon.api.pin.rm(testFolder.hash)
112+
})
113+
.then(() => daemon.api.pin.ls())
114+
}
115+
116+
return withDaemons(pipeline)
117+
.then(([goPins, jsPins]) => {
118+
expect(goPins.length).to.eql(0)
119+
expect(jsPins.length).to.eql(0)
120+
})
121+
})
122+
123+
// When a pin contains the root of another pin and we remove it, it is
124+
// instead kept but its type is changed to 'indirect'
125+
it('remove a child shared by multiple pins', function () {
126+
this.timeout(20 * 1000)
127+
this.slow(20 * 1000)
128+
129+
let jupiterDir
130+
function pipeline (daemon) {
131+
return daemon.api.add(jupiter, { pin: false })
132+
.then(chunks => {
133+
jupiterDir = jupiterDir ||
134+
chunks.find(chunk => chunk.path === 'test/fixtures/planets')
135+
136+
// by separately pinning all the DAG nodes created when adding,
137+
// dirs are pinned as type=recursive and
138+
// nested pins reference each other
139+
return daemon.api.pin.add(chunks.map(chunk => chunk.hash))
140+
})
141+
.then(() => daemon.api.pin.rm(jupiterDir.hash))
142+
.then(() => daemon.api.pin.ls())
143+
}
144+
145+
return withDaemons(pipeline)
146+
.then(([goPins, jsPins]) => {
147+
expect(goPins.length).to.be.gt(0)
148+
expect(goPins).to.deep.include.members(jsPins)
149+
expect(jsPins).to.deep.include.members(goPins)
150+
151+
const dirPin = goPins.find(pin => pin.hash === jupiterDir.hash)
152+
expect(dirPin.type).to.eql('indirect')
153+
})
154+
})
155+
})
156+
157+
describe('ls', function () {
158+
it('print same pins', function () {
159+
this.timeout(30 * 1000)
160+
161+
function pipeline (daemon) {
162+
return daemon.api.add(jupiter)
163+
.then(() => daemon.api.pin.ls())
164+
}
165+
166+
return withDaemons(pipeline)
167+
.then(([goPins, jsPins]) => {
168+
expect(goPins.length).to.be.gt(0)
169+
expect(goPins).to.deep.include.members(jsPins)
170+
expect(jsPins).to.deep.include.members(goPins)
171+
})
172+
})
173+
})
174+
175+
describe(`go and js pinset storage are compatible`, function () {
176+
function pipeline (options) {
177+
// by starting each daemon with the same repoPath, they
178+
// will read/write pins from the same datastore.
179+
const repoPath = utils.tmpPath()
180+
const content = Buffer.from(String(Math.random()))
181+
const pins = []
182+
183+
return spawnAndStart(options.first, repoPath)
184+
.then(daemon => {
185+
return daemon.api.add(content)
186+
.then(() => daemon.api.pin.ls())
187+
})
188+
.then(ls => pins.push(ls))
189+
.then(() => utils.stopDaemons(daemons))
190+
.then(() => spawnAndStart(options.second, repoPath))
191+
.then(daemon => daemon.api.pin.ls())
192+
.then(ls => pins.push(ls))
193+
.then(() => pins)
194+
}
195+
196+
// js-ipfs can read pins stored by go-ipfs
197+
// tests that go's pin.flush and js' pin.load are compatible
198+
it('go -> js', function () {
199+
this.timeout(20 * 1000)
200+
this.slow(15000)
201+
202+
return pipeline({ first: 'go', second: 'js' })
203+
.then(([goPins, jsPins]) => {
204+
expect(goPins.length).to.be.gt(0)
205+
expect(jsPins).to.deep.include.members(goPins)
206+
expect(goPins).to.deep.include.members(jsPins)
207+
})
208+
})
209+
210+
// go-ipfs can read pins stored by js-ipfs
211+
// tests that js' pin.flush and go's pin.load are compatible
212+
it.skip('js -> go', function () {
213+
// skipped because go can not be spawned on a js repo due to changes in
214+
// the DataStore config [link]
215+
this.timeout(20 * 1000)
216+
this.slow(15000)
217+
218+
return pipeline({ first: 'js', second: 'go' })
219+
.then(([jsPins, goPins]) => {
220+
expect(jsPins.length).to.be.gt(0)
221+
expect(goPins).to.deep.include.members(jsPins)
222+
expect(jsPins).to.deep.include.members(goPins)
223+
})
224+
})
225+
})
226+
})

test/utils/pin-utils.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict'
2+
3+
const os = require('os')
4+
const path = require('path')
5+
const hat = require('hat')
6+
7+
exports.removeAllPins = function removeAllPins (daemon) {
8+
return daemon.api.pin.ls()
9+
.then(pins => {
10+
const rootPins = pins.filter(
11+
pin => pin.type === 'recursive' || pin.type === 'direct'
12+
)
13+
return Promise.all(rootPins.map(pin => daemon.api.pin.rm(pin.hash)))
14+
})
15+
.then(() => daemon)
16+
}
17+
18+
exports.stopDaemons = function stopDaemons (daemons) {
19+
return Promise.all(
20+
daemons.map(daemon => new Promise((resolve, reject) =>
21+
daemon.stop(err => err ? reject(err) : resolve())
22+
))
23+
)
24+
}
25+
26+
exports.tmpPath = function tmpPath () {
27+
return path.join(os.tmpdir(), hat())
28+
}

0 commit comments

Comments
 (0)