Skip to content

Commit 3f26383

Browse files
se030wcho21
andauthored
Test/#333-K: CRDT 테스트 케이스 추가 (#337)
* test: remoteDeletion에 대한 convergence 테스트 * docs: 주석으로 테스트케이스 의존성 명시 * chore: test 폴더 생성 및 utils 분리 * refactor: convergenceCheck 메서드 개선 확인이 필요한 remote site CRDT들을 매개변수로 받도록 변경 * test: 예외 케이스 테스트 * fix: 타입 에러 수정 * chore: tsconfig 추가 필요한 파일인데 서버 빌드하면서 삭제했나봐요 * fix: 테스트 케이스 설명 수정 * Test/C: CRDT 테스트 케이스 수정 및 예외 케이스 추가 (#362) * test: 의도가 잘 보일 수 있도록 수정 및 예외 케이스 추가 * fix: localDelete 테스트 메서드 수정 및 에러 throw Co-authored-by: W. Cho <[email protected]> * refactor: convergence test에 beforeEach-arrange 추가 Co-authored-by: W. Cho <[email protected]>
1 parent 34b6f92 commit 3f26383

File tree

8 files changed

+243
-120
lines changed

8 files changed

+243
-120
lines changed

@wabinar/crdt/convergence.test.ts

Lines changed: 0 additions & 83 deletions
This file was deleted.

@wabinar/crdt/crdt.test.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

@wabinar/crdt/linked-list.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ export default class LinkedList {
111111

112112
const prevNode = this.findByIndex(index - 1);
113113

114-
if (!prevNode.next) return null;
114+
if (!prevNode.next) throw new Error();
115115

116116
const targetNode = this.getNode(prevNode.next);
117117

118-
if (!targetNode) return null;
118+
if (!targetNode) throw new Error();
119119

120120
this.deleteNode(targetNode.id);
121121
prevNode.next = targetNode.next;
@@ -215,7 +215,7 @@ export default class LinkedList {
215215

216216
spread(): string[] {
217217
let node: Node | null = this.getHeadNode();
218-
let result = [];
218+
let result: string[] = [];
219219

220220
while (node) {
221221
result.push(node.value);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import CRDT from '../index';
2+
import LinkedList from '../linked-list';
3+
import { convergenceCheck, remoteInsertThroughSocket } from './utils';
4+
5+
let 원희, 주영, 도훈, 세영, remoteSites;
6+
7+
const arrange = () => {
8+
const 원희remotes = [원희.localInsert(-1, '녕'), 원희.localInsert(-1, '안')];
9+
10+
[주영, 도훈, 세영].forEach(() => {
11+
원희remotes.forEach((op) => remoteInsertThroughSocket(, op));
12+
});
13+
};
14+
15+
describe('Convergence', () => {
16+
beforeEach(() => {
17+
원희 = new CRDT(1, new LinkedList());
18+
주영 = new CRDT(2, new LinkedList());
19+
도훈 = new CRDT(3, new LinkedList());
20+
세영 = new CRDT(4, new LinkedList());
21+
22+
remoteSites = [원희, 주영, 도훈, 세영];
23+
24+
arrange();
25+
});
26+
27+
it('하나의 site에서 삽입', () => {
28+
arrange();
29+
30+
convergenceCheck(remoteSites);
31+
});
32+
33+
it('여러 site에서 같은 위치에 삽입', () => {
34+
const 도훈remote = 도훈.localInsert(0, '왭');
35+
const 세영remote = 세영.localInsert(0, '?');
36+
37+
remoteInsertThroughSocket(도훈, 세영remote);
38+
remoteInsertThroughSocket(세영, 도훈remote);
39+
40+
[원희, 주영].forEach(() => {
41+
remoteInsertThroughSocket(, 도훈remote);
42+
remoteInsertThroughSocket(, 세영remote);
43+
});
44+
45+
convergenceCheck(remoteSites);
46+
});
47+
48+
it('여러 site에서 다른 위치에 삽입', () => {
49+
const 원희remote = 원희.localInsert(0, '네');
50+
const 주영remote = 주영.localInsert(1, '!');
51+
52+
remoteInsertThroughSocket(원희, 주영remote);
53+
remoteInsertThroughSocket(주영, 원희remote);
54+
55+
[도훈, 세영].forEach(() => {
56+
remoteInsertThroughSocket(, 원희remote);
57+
remoteInsertThroughSocket(, 주영remote);
58+
});
59+
60+
convergenceCheck(remoteSites);
61+
});
62+
63+
it('여러 site에서 같은 위치 삭제', () => {
64+
const 도훈remote = 도훈.localDelete(0);
65+
const 세영remote = 세영.localDelete(0);
66+
67+
도훈.remoteDelete(세영remote);
68+
세영.remoteDelete(도훈remote);
69+
70+
[원희, 주영].forEach(() => {
71+
.remoteDelete(세영remote);
72+
.remoteDelete(도훈remote);
73+
});
74+
75+
convergenceCheck(remoteSites);
76+
});
77+
78+
it('여러 site에서 다른 위치 삭제', () => {
79+
const 원희remote = 원희.localDelete(0);
80+
const 주영remote = 주영.localDelete(1);
81+
82+
원희.remoteDelete(주영remote);
83+
주영.remoteDelete(원희remote);
84+
85+
[도훈, 세영].forEach(() => {
86+
.remoteDelete(원희remote);
87+
.remoteDelete(주영remote);
88+
});
89+
90+
convergenceCheck(remoteSites);
91+
});
92+
});

@wabinar/crdt/test/crdt.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import CRDT from '../index';
2+
import LinkedList from '../linked-list';
3+
4+
describe('CRDT Local operations', () => {
5+
let ;
6+
7+
beforeEach(() => {
8+
const initialStructure = new LinkedList();
9+
= new CRDT(1, initialStructure);
10+
});
11+
12+
describe('localInsert()', () => {
13+
it('head 위치 삽입에 성공한다.', () => {
14+
// act
15+
.localInsert(-1, '녕');
16+
.localInsert(-1, '안');
17+
18+
// assert
19+
expect(.read()).toEqual('안녕');
20+
});
21+
22+
it('tail 위치 삽입에 성공한다.', () => {
23+
// act
24+
.localInsert(-1, '안');
25+
.localInsert(0, '녕');
26+
27+
// assert
28+
expect(.read()).toEqual('안녕');
29+
});
30+
31+
it('없는 위치에 삽입 시도 시 에러를 던진다.', () => {
32+
// arrange
33+
.localInsert(-1, '안');
34+
35+
// act & assert
36+
expect(() => .localInsert(1, '녕')).toThrow();
37+
});
38+
});
39+
40+
describe('localDelete()', () => {
41+
it('tail 위치 삭제에 성공한다.', () => {
42+
// arrange
43+
.localInsert(-1, '안');
44+
.localInsert(0, '녕');
45+
46+
// act
47+
.localDelete(1);
48+
49+
// assert
50+
expect(.read()).toEqual('안');
51+
});
52+
53+
it('없는 위치에 삭제 시도 시 에러를 던진다.', () => {
54+
// arrange
55+
.localInsert(-1, '안');
56+
57+
// act & assert
58+
expect(() => .localDelete(1)).toThrow();
59+
});
60+
});
61+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import CRDT from '../index';
2+
import LinkedList from '../linked-list';
3+
import { remoteInsertThroughSocket } from './utils';
4+
5+
describe('Exceptions', () => {
6+
let 도훈, 호둔, remoteSites;
7+
8+
beforeEach(() => {
9+
도훈 = new CRDT(1, new LinkedList());
10+
호둔 = new CRDT(2, new LinkedList());
11+
12+
remoteSites = [도훈, 호둔];
13+
14+
const 도훈remotes = [
15+
도훈.localInsert(-1, '녕'),
16+
도훈.localInsert(-1, '안'),
17+
];
18+
도훈remotes.forEach((op) => remoteInsertThroughSocket(호둔, op));
19+
});
20+
21+
it('존재하지 않는 인덱스에 localInsert', () => {
22+
expect(() => 도훈.localInsert(100, '.')).toThrow();
23+
});
24+
25+
it('존재하지 않는 노드 뒤에 remoteInsert', () => {
26+
const 도훈remotes = [
27+
도훈.localInsert(1, '.'),
28+
도훈.localInsert(2, '.'),
29+
도훈.localInsert(3, '.'),
30+
];
31+
32+
// 연속된 remote operation 순서가 섞이는 케이스
33+
expect(() => 호둔.remoteInsert(도훈remotes[1])).toThrow();
34+
});
35+
36+
it('local에서 삭제된 노드 뒤에 삽입', () => {
37+
const 도훈remote = 도훈.localInsert(0, '!');
38+
const 호둔remote = 호둔.localDelete(0);
39+
40+
expect(() => 호둔.remoteInsert(도훈remote)).toThrow();
41+
});
42+
});

@wabinar/crdt/test/utils.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import CRDT from '../index';
2+
import { RemoteInsertOperation } from '../linked-list';
3+
import { Node } from '../node';
4+
5+
/**
6+
* 모든 remote site 문자열이 일치하는지 확인
7+
*/
8+
export const convergenceCheck = (remoteSites) => {
9+
const convergenceSet = remoteSites.reduce((prev, cur, index) => {
10+
if (index < remoteSites.length - 1) {
11+
prev.push([cur, remoteSites[index + 1]]);
12+
}
13+
14+
return prev;
15+
}, []);
16+
17+
convergenceSet.forEach(([first, second]) => {
18+
expect(first.read()).toEqual(second.read());
19+
});
20+
};
21+
22+
/**
23+
* 바로 전달하면 같은 인스턴스를 가리키게 되어 remote operation 의미가 사라짐
24+
*/
25+
const deepCopyRemoteInsertion = (op: RemoteInsertOperation) => {
26+
const { node } = op;
27+
28+
const copy = { ...node };
29+
Object.setPrototypeOf(copy, Node.prototype);
30+
31+
return { node: copy as Node };
32+
};
33+
34+
export const remoteInsertThroughSocket = (
35+
crdt: CRDT,
36+
op: RemoteInsertOperation,
37+
) => {
38+
const copy = deepCopyRemoteInsertion(op);
39+
crdt.remoteInsert(copy);
40+
};

@wabinar/crdt/tsconfig.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"compilerOptions": {
3+
"esModuleInterop": true
4+
}
5+
}

0 commit comments

Comments
 (0)