Skip to content

Commit af2c95e

Browse files
committed
couchnode: add quick test for release verification
1 parent 8af20f6 commit af2c95e

File tree

1 file changed

+389
-0
lines changed

1 file changed

+389
-0
lines changed
Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
const os = require('os')
2+
const path = require('path')
3+
const fs = require('fs')
4+
const http = require('http')
5+
const net = require('net')
6+
const child_process = require('child_process')
7+
const couchbase = require('couchbase')
8+
const assert = require('assert')
9+
10+
class MockServerSetup {
11+
mockUrlBase = 'http://packages.couchbase.com/clients/c/mock'
12+
mockPath = ''
13+
constructor(mockUrl, mockVersion) {
14+
if (typeof mockVersion === 'undefined') {
15+
this.mockVersion = '1.5.25'
16+
this.mockFile = `CouchbaseMock-${this.mockVersion}.jar`
17+
}
18+
if (typeof mockUrl === 'undefined') {
19+
this.mockUrl = `${this.mockUrlBase}/${this.mockFile}`
20+
}
21+
}
22+
23+
getMockJar(callback) {
24+
const mockPath = path.join(os.tmpdir(), this.mockFile)
25+
// Check if the file already exists
26+
27+
const self = this
28+
fs.stat(mockPath, (err, stats) => {
29+
if (!err && stats.isFile() && stats.size > 0) {
30+
callback(null, mockPath)
31+
return
32+
}
33+
34+
// Remove whatever was there
35+
fs.unlink(mockPath, () => {
36+
// we ignore any errors here...
37+
console.log('downloading ' + self.mockUrl + ' to ' + mockPath)
38+
39+
const file = fs.createWriteStream(mockPath)
40+
http.get(self.mockUrl, (res) => {
41+
if (res.statusCode !== 200) {
42+
callback(new Error('failed to get mock from server'))
43+
return
44+
}
45+
46+
res
47+
.on('data', (data) => {
48+
file.write(data)
49+
})
50+
.on('end', () => {
51+
file.end(() => {
52+
callback(null, mockPath)
53+
})
54+
})
55+
})
56+
})
57+
})
58+
}
59+
60+
async getMock() {
61+
const self = this
62+
return new Promise((resolve, reject) => {
63+
self.getMockJar((err, mockPath) => {
64+
if (err) {
65+
reject(err)
66+
return
67+
}
68+
self.startMock(mockPath, (err, mock) => {
69+
if (err) {
70+
reject(err)
71+
return
72+
}
73+
resolve(mock)
74+
})
75+
})
76+
})
77+
}
78+
79+
async sendMockCmd(mock, cmd, payload) {
80+
return new Promise((resolve, reject) => {
81+
mock.command(cmd, payload, (err, res) => {
82+
if (err) {
83+
reject(err)
84+
return
85+
}
86+
87+
resolve(res)
88+
})
89+
})
90+
}
91+
92+
startMock(mockPath, callback) {
93+
const self = this
94+
const server = net.createServer((socket) => {
95+
// Close the socket immediately
96+
server.close()
97+
98+
let readBuf = null
99+
let mockPort = -1
100+
let msgHandlers = []
101+
socket.on('data', (data) => {
102+
if (readBuf) {
103+
readBuf = Buffer.concat([readBuf, data])
104+
} else {
105+
readBuf = data
106+
}
107+
108+
while (readBuf.length > 0) {
109+
if (mockPort === -1) {
110+
var nullIdx = self._bufferIndexOf(readBuf, 0)
111+
if (nullIdx >= 0) {
112+
var portStr = readBuf.slice(0, nullIdx)
113+
readBuf = readBuf.slice(nullIdx + 1)
114+
mockPort = parseInt(portStr, 10)
115+
116+
socket.entryPort = mockPort
117+
118+
callback(null, socket)
119+
continue
120+
}
121+
} else {
122+
var termIdx = self._bufferIndexOf(readBuf, '\n')
123+
if (termIdx >= 0) {
124+
var msgBuf = readBuf.slice(0, termIdx)
125+
readBuf = readBuf.slice(termIdx + 1)
126+
127+
var msg = JSON.parse(msgBuf.toString())
128+
129+
if (msgHandlers.length === 0) {
130+
console.error('mock response with no handler')
131+
continue
132+
}
133+
var msgHandler = msgHandlers.shift()
134+
135+
if (msg.status === 'ok') {
136+
msgHandler(null, msg.payload)
137+
} else {
138+
var err = new Error('mock error: ' + msg.error)
139+
msgHandler(err, null)
140+
}
141+
142+
continue
143+
}
144+
}
145+
break
146+
}
147+
})
148+
socket.on('error', (err) => {
149+
if (socket.userClosed) {
150+
return
151+
}
152+
153+
console.error('mocksock err', err)
154+
})
155+
socket.command = (cmdName, payload, callback) => {
156+
if (callback === undefined) {
157+
callback = payload
158+
payload = undefined
159+
}
160+
161+
msgHandlers.push(callback)
162+
var dataOut =
163+
JSON.stringify({
164+
command: cmdName,
165+
payload: payload,
166+
}) + '\n'
167+
socket.write(dataOut)
168+
}
169+
socket.close = () => {
170+
socket.userClosed = true
171+
socket.end()
172+
}
173+
console.log('got mock server connection')
174+
})
175+
176+
server.on('error', (err) => {
177+
callback(err)
178+
})
179+
180+
server.on('listening', () => {
181+
var ctlPort = server.address().port
182+
183+
var javaOpts = [
184+
'-jar',
185+
mockPath,
186+
'--cccp',
187+
'--harakiri-monitor',
188+
'localhost:' + ctlPort,
189+
'--port',
190+
'0',
191+
'--replicas',
192+
'1',
193+
'--vbuckets',
194+
'32',
195+
'--nodes',
196+
'3',
197+
'--buckets',
198+
'default::couchbase',
199+
]
200+
console.log('launching mock:', javaOpts)
201+
202+
// Start Java Mock Here...
203+
var mockproc = child_process.spawn('java', javaOpts)
204+
mockproc.on('error', (err) => {
205+
server.close()
206+
callback(err)
207+
return
208+
})
209+
mockproc.stderr.on('data', (data) => {
210+
console.error('mockproc err: ' + data.toString())
211+
})
212+
mockproc.on('close', (code) => {
213+
if (code !== 0 && code !== 1) {
214+
console.log('mock closed with non-zero exit code: ' + code)
215+
}
216+
server.close()
217+
})
218+
219+
mockproc.stdout.on('data', (data) => {
220+
console.log(data)
221+
})
222+
})
223+
224+
server.listen()
225+
}
226+
227+
_bufferIndexOf(buffer, search) {
228+
if (buffer.indexOf instanceof Function) {
229+
return buffer.indexOf(search)
230+
} else {
231+
if (typeof search === 'string') {
232+
search = Buffer.from(search)
233+
} else if (typeof search === 'number' && !isNaN(search)) {
234+
search = Buffer.from([search])
235+
}
236+
for (var i = 0; i < buffer.length; ++i) {
237+
if (buffer[i] === search[0]) {
238+
return i
239+
}
240+
}
241+
return -1
242+
}
243+
}
244+
}
245+
246+
async function runSmokeTest() {
247+
let mockSetup
248+
let mock
249+
let ports
250+
let success = false
251+
for (let i = 0; i < 3; ++i) {
252+
try {
253+
mockSetup = new MockServerSetup()
254+
mock = await mockSetup.getMock()
255+
ports = await mockSetup.sendMockCmd(mock, 'get_mcports')
256+
break
257+
} catch (err) {
258+
console.error('Mock startup failed:', err)
259+
}
260+
}
261+
262+
if(!mock){
263+
console.log('Unable to create mock server.')
264+
return false
265+
}
266+
267+
try {
268+
let serverList = []
269+
for (let portIdx = 0; portIdx < ports.length; ++portIdx) {
270+
serverList.push('localhost:' + ports[portIdx])
271+
}
272+
273+
connstr = 'couchbase://' + serverList.join(',')
274+
const cluster = await couchbase.connect(connstr, {
275+
username: 'Administrator',
276+
password: 'password',
277+
})
278+
279+
const bucket = cluster.bucket('default')
280+
const collection = bucket.defaultCollection()
281+
282+
const key = 'test-key'
283+
let doc = {
284+
a: 'aa',
285+
b: 1,
286+
c: ['foo', 'bar'],
287+
d: { baz: 'quz' },
288+
what: 'this is a test!',
289+
}
290+
291+
let caughtError = false
292+
try {
293+
await collection.remove(key)
294+
} catch (err) {
295+
caughtError = true
296+
assert(
297+
err instanceof couchbase.DocumentNotFoundError,
298+
'remove() did not raise DocumentNotFoundError'
299+
)
300+
}
301+
assert.equal(caughtError, true, 'remove() did not raise an error')
302+
caughtError = false
303+
304+
let res = await collection.insert(key, doc)
305+
assert(
306+
res instanceof couchbase.MutationResult,
307+
'insert() did not return MutationResult'
308+
)
309+
assert.ok(res.cas, `insert() returned result w/ cas=${res.cas}`)
310+
311+
res = await collection.get(key)
312+
assert(res instanceof couchbase.GetResult)
313+
assert.ok(res.cas, `get() returned result w/ cas=${res.cas}`)
314+
assert.deepStrictEqual(res.content, doc, 'get() returned incorrect result')
315+
316+
try {
317+
res = await collection.insert(key, doc)
318+
} catch (err) {
319+
caughtError = true
320+
assert(
321+
err instanceof couchbase.DocumentExistsError,
322+
'insert() did not raise DocumentExistsError'
323+
)
324+
}
325+
assert.equal(caughtError, true, 'insert() did not raise an error')
326+
caughtError = false
327+
328+
doc.what = 'This is an upsert test!'
329+
res = await collection.upsert(key, doc)
330+
assert(
331+
res instanceof couchbase.MutationResult,
332+
'upsert() did not return MutationResult'
333+
)
334+
assert.ok(res.cas, `upsert() returned result w/ cas=${res.cas}`)
335+
336+
res = await collection.get(key)
337+
assert(res instanceof couchbase.GetResult)
338+
assert.ok(res.cas, `get() returned result w/ cas=${res.cas}`)
339+
assert.deepStrictEqual(res.content, doc, 'get() returned incorrect result')
340+
341+
doc.what = 'This is an replace test!'
342+
res = await collection.replace(key, doc)
343+
assert(
344+
res instanceof couchbase.MutationResult,
345+
'replace() did not return MutationResult'
346+
)
347+
assert.ok(res.cas, `replace() returned result w/ cas=${res.cas}`)
348+
349+
res = await collection.get(key)
350+
assert(res instanceof couchbase.GetResult)
351+
assert.ok(res.cas, `get() returned result w/ cas=${res.cas}`)
352+
assert.deepStrictEqual(res.content, doc, 'get() returned incorrect result')
353+
354+
res = await collection.remove(key, doc)
355+
assert(
356+
res instanceof couchbase.MutationResult,
357+
'remove() did not return MutationResult'
358+
)
359+
assert.ok(res.cas, `remove() returned result w/ cas=${res.cas}`)
360+
361+
try {
362+
res = await collection.get(key)
363+
} catch (err) {
364+
caughtError = true
365+
assert(
366+
err instanceof couchbase.DocumentNotFoundError,
367+
'get() did not raise DocumentNotFoundError'
368+
)
369+
}
370+
assert.equal(caughtError, true, 'get() did not raise an error')
371+
caughtError = false
372+
success = true
373+
} catch (err) {
374+
console.error('Err:', err)
375+
} finally {
376+
if (mock) {
377+
mock.close()
378+
}
379+
}
380+
return success
381+
}
382+
383+
runSmokeTest()
384+
.then((success) => {
385+
console.log(`Smoke test finished. Success=${success}.`)
386+
})
387+
.catch((err) => {
388+
console.error('Err:', err)
389+
})

0 commit comments

Comments
 (0)