trie.test.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import { createTrie } from './trie';
  2. // eslint-disable-next-line import-x/no-unresolved -- fuck eslint-import
  3. import { describe, expect, it } from 'bun:test';
  4. describe('Trie', () => {
  5. it('should be possible to add items to a Trie.', () => {
  6. const trie = createTrie();
  7. trie.add('sukka');
  8. trie.add('ukka');
  9. trie.add('akku');
  10. expect(trie.size).toBe(3);
  11. expect(trie.has('sukka')).toBeTrue();
  12. expect(trie.has('ukka')).toBeTrue();
  13. expect(trie.has('akku')).toBeTrue();
  14. expect(trie.has('noc')).toBeFalse();
  15. expect(trie.has('suk')).toBeFalse();
  16. expect(trie.has('sukkaw')).toBeFalse();
  17. });
  18. it('should be possible to add domains to a Trie (hostname).', () => {
  19. const trie = createTrie(null, true);
  20. trie.add('a.skk.moe');
  21. trie.add('skk.moe');
  22. trie.add('anotherskk.moe');
  23. expect(trie.size).toBe(3);
  24. expect(trie.has('a.skk.moe')).toBeTrue();
  25. expect(trie.has('skk.moe')).toBeTrue();
  26. expect(trie.has('anotherskk.moe')).toBeTrue();
  27. expect(trie.has('example.com')).toBeFalse();
  28. expect(trie.has('skk.mo')).toBeFalse();
  29. expect(trie.has('another.skk.moe')).toBeFalse();
  30. });
  31. it('adding the same item several times should not increase size.', () => {
  32. const trie = createTrie();
  33. trie.add('rat');
  34. trie.add('erat');
  35. trie.add('rat');
  36. expect(trie.size).toBe(2);
  37. expect(trie.has('rat')).toBeTrue();
  38. });
  39. it('adding the same item several times should not increase size (hostname).', () => {
  40. const trie = createTrie(null, true);
  41. trie.add('skk.moe');
  42. trie.add('blog.skk.moe');
  43. trie.add('skk.moe');
  44. expect(trie.size).toBe(2);
  45. expect(trie.has('skk.moe')).toBeTrue();
  46. });
  47. it('should be possible to set the null sequence.', () => {
  48. let trie = createTrie();
  49. trie.add('');
  50. expect(trie.has('')).toBeTrue();
  51. trie = createTrie(null, true);
  52. trie.add('');
  53. expect(trie.has('')).toBeTrue();
  54. });
  55. it('should be possible to delete items.', () => {
  56. const trie = createTrie();
  57. trie.add('rat');
  58. trie.add('rate');
  59. trie.add('tar');
  60. expect(trie.delete('')).toBeFalse();
  61. expect(trie.delete('')).toBeFalse();
  62. expect(trie.delete('hello')).toBeFalse();
  63. expect(trie.delete('rat')).toBeTrue();
  64. expect(trie.has('rat')).toBeFalse();
  65. expect(trie.has('rate')).toBeTrue();
  66. expect(trie.size).toBe(2);
  67. expect(trie.delete('rate')).toBeTrue();
  68. expect(trie.size).toBe(1);
  69. expect(trie.delete('tar')).toBeTrue();
  70. expect(trie.size).toBe(0);
  71. });
  72. it('should be possible to delete items (hostname).', () => {
  73. const trie = createTrie(null, true);
  74. trie.add('skk.moe');
  75. trie.add('example.com');
  76. trie.add('moe.sb');
  77. expect(trie.delete('')).toBeFalse();
  78. expect(trie.delete('')).toBeFalse();
  79. expect(trie.delete('example.org')).toBeFalse();
  80. expect(trie.delete('skk.moe')).toBeTrue();
  81. expect(trie.has('skk.moe')).toBeFalse();
  82. expect(trie.has('moe.sb')).toBeTrue();
  83. expect(trie.size).toBe(2);
  84. expect(trie.delete('example.com')).toBeTrue();
  85. expect(trie.size).toBe(1);
  86. expect(trie.delete('moe.sb')).toBeTrue();
  87. expect(trie.size).toBe(0);
  88. });
  89. it('should be possible to check the existence of a sequence in the Trie.', () => {
  90. const trie = createTrie();
  91. trie.add('romanesque');
  92. expect(trie.has('romanesque')).toBe(true);
  93. expect(trie.has('roman')).toBe(false);
  94. expect(trie.has('esque')).toBe(false);
  95. expect(trie.has('')).toBe(false);
  96. });
  97. it('should be possible to check the existence of a sequence in the Trie (hostname).', () => {
  98. const trie = createTrie(null, true);
  99. trie.add('example.org.skk.moe');
  100. expect(trie.has('example.org.skk.moe')).toBe(true);
  101. expect(trie.has('skk.moe')).toBe(false);
  102. expect(trie.has('example.org')).toBe(false);
  103. expect(trie.has('')).toBe(false);
  104. });
  105. it('should be possible to retrieve items matching the given prefix.', () => {
  106. const trie = createTrie();
  107. trie.add('roman');
  108. trie.add('esqueroman');
  109. trie.add('sesqueroman');
  110. trie.add('greek');
  111. expect(trie.find('roman')).toEqual(['roman', 'esqueroman', 'sesqueroman']);
  112. expect(trie.find('man')).toEqual(['roman', 'esqueroman', 'sesqueroman']);
  113. expect(trie.find('esqueroman')).toEqual(['esqueroman', 'sesqueroman']);
  114. expect(trie.find('eek')).toEqual(['greek']);
  115. expect(trie.find('hello')).toEqual([]);
  116. expect(trie.find('')).toEqual(['greek', 'roman', 'esqueroman', 'sesqueroman']);
  117. });
  118. it('should be possible to retrieve items matching the given prefix (hostname).', () => {
  119. const trie = createTrie(null, true);
  120. trie.add('example.com');
  121. trie.add('blog.example.com');
  122. trie.add('cdn.example.com');
  123. trie.add('example.org');
  124. expect(trie.find('example.com')).toEqual(['example.com', 'cdn.example.com', 'blog.example.com']);
  125. expect(trie.find('com')).toEqual(['example.com', 'cdn.example.com', 'blog.example.com']);
  126. expect(trie.find('.example.com')).toEqual(['cdn.example.com', 'blog.example.com']);
  127. expect(trie.find('org')).toEqual(['example.org']);
  128. expect(trie.find('example.net')).toEqual([]);
  129. expect(trie.find('')).toEqual(['example.org', 'example.com', 'cdn.example.com', 'blog.example.com']);
  130. });
  131. it('should be possible to create a trie from an arbitrary iterable.', () => {
  132. let trie = createTrie(['roman', 'esqueroman']);
  133. expect(trie.size).toBe(2);
  134. expect(trie.has('roman')).toBe(true);
  135. trie = createTrie(new Set(['skk.moe', 'example.com']), true);
  136. expect(trie.size).toBe(2);
  137. expect(trie.has('skk.moe')).toBe(true);
  138. });
  139. });
  140. describe.each([
  141. ['hostname mode off', false],
  142. ['hostname mode on', true]
  143. ])('surge domainset dedupe %s', (_, hostnameMode) => {
  144. it('should not remove same entry', () => {
  145. const trie = createTrie(['.skk.moe', 'noc.one'], hostnameMode);
  146. expect(trie.find('.skk.moe')).toStrictEqual(['.skk.moe']);
  147. expect(trie.find('noc.one')).toStrictEqual(['noc.one']);
  148. });
  149. it('should match subdomain - 1', () => {
  150. const trie = createTrie(['www.noc.one', 'www.sukkaw.com', 'blog.skk.moe', 'image.cdn.skk.moe', 'cdn.sukkaw.net'], hostnameMode);
  151. expect(trie.find('.skk.moe')).toStrictEqual(['image.cdn.skk.moe', 'blog.skk.moe']);
  152. expect(trie.find('.sukkaw.com')).toStrictEqual(['www.sukkaw.com']);
  153. });
  154. it('should match subdomain - 2', () => {
  155. const trie = createTrie(['www.noc.one', 'www.sukkaw.com', '.skk.moe', 'blog.skk.moe', 'image.cdn.skk.moe', 'cdn.sukkaw.net'], hostnameMode);
  156. expect(trie.find('.skk.moe')).toStrictEqual(['.skk.moe', 'image.cdn.skk.moe', 'blog.skk.moe']);
  157. expect(trie.find('.sukkaw.com')).toStrictEqual(['www.sukkaw.com']);
  158. });
  159. it('should not remove non-subdomain', () => {
  160. const trie = createTrie(['skk.moe', 'sukkaskk.moe'], hostnameMode);
  161. expect(trie.find('.skk.moe')).toStrictEqual([]);
  162. });
  163. });
  164. describe('smol tree', () => {
  165. it('should create simple tree - 1', () => {
  166. const trie = createTrie([
  167. '.skk.moe', 'blog.skk.moe', '.cdn.skk.moe', 'skk.moe',
  168. 'www.noc.one', 'cdn.noc.one',
  169. '.blog.sub.example.com', 'sub.example.com', 'cdn.sub.example.com', '.sub.example.com'
  170. ], true, true);
  171. expect(trie.dump()).toStrictEqual([
  172. '.sub.example.com',
  173. 'cdn.noc.one', 'www.noc.one',
  174. '.skk.moe'
  175. ]);
  176. });
  177. it('should create simple tree - 2', () => {
  178. const trie = createTrie([
  179. '.skk.moe', 'blog.skk.moe', '.cdn.skk.moe', 'skk.moe'
  180. ], true, true);
  181. expect(trie.dump()).toStrictEqual([
  182. '.skk.moe'
  183. ]);
  184. });
  185. it('should create simple tree - 2', () => {
  186. const trie = createTrie([
  187. '.blog.sub.example.com', 'cdn.sub.example.com', '.sub.example.com'
  188. ], true, true);
  189. expect(trie.dump()).toStrictEqual([
  190. '.sub.example.com'
  191. ]);
  192. trie.add('.sub.example.com');
  193. expect(trie.dump()).toStrictEqual([
  194. '.sub.example.com'
  195. ]);
  196. });
  197. it('should create simple tree - 3', () => {
  198. const trie = createTrie([
  199. 'commercial.shouji.360.cn',
  200. 'act.commercial.shouji.360.cn',
  201. 'cdn.creative.medialytics.com',
  202. 'px.cdn.creative.medialytics.com'
  203. ], true, true);
  204. expect(trie.dump()).toStrictEqual([
  205. 'cdn.creative.medialytics.com',
  206. 'px.cdn.creative.medialytics.com',
  207. 'commercial.shouji.360.cn',
  208. 'act.commercial.shouji.360.cn'
  209. ]);
  210. });
  211. it('should dedupe subdomain properly', () => {
  212. const trie = createTrie([
  213. 'skk.moe',
  214. 'anotherskk.moe',
  215. 'blog.anotherskk.moe',
  216. 'blog.skk.moe'
  217. ], true, true);
  218. expect(trie.dump()).toStrictEqual([
  219. 'anotherskk.moe',
  220. 'blog.anotherskk.moe',
  221. 'skk.moe',
  222. 'blog.skk.moe'
  223. ]);
  224. });
  225. it('should efficiently whitelist domains', () => {
  226. const trie = createTrie([
  227. 'skk.moe',
  228. 'anotherskk.moe',
  229. 'blog.anotherskk.moe',
  230. 'blog.skk.moe'
  231. ], true, true);
  232. expect(trie.dump()).toStrictEqual([
  233. 'anotherskk.moe',
  234. 'blog.anotherskk.moe',
  235. 'skk.moe',
  236. 'blog.skk.moe'
  237. ]);
  238. trie.whitelist('.skk.moe');
  239. expect(trie.dump()).toStrictEqual([
  240. 'anotherskk.moe',
  241. 'blog.anotherskk.moe'
  242. ]);
  243. trie.whitelist('anotherskk.moe');
  244. expect(trie.dump()).toStrictEqual([
  245. 'blog.anotherskk.moe'
  246. ]);
  247. trie.add('anotherskk.moe');
  248. trie.whitelist('.anotherskk.moe');
  249. expect(trie.dump()).toStrictEqual([]);
  250. });
  251. it('should whitelist trie correctly', () => {
  252. const trie = createTrie([
  253. '.t.co',
  254. 't.co',
  255. 'example.t.co',
  256. '.skk.moe',
  257. 'blog.cdn.example.com',
  258. 'cdn.example.com'
  259. ], true, true);
  260. expect(trie.dump()).toStrictEqual([
  261. 'cdn.example.com', 'blog.cdn.example.com',
  262. '.skk.moe',
  263. '.t.co'
  264. ]);
  265. trie.whitelist('.t.co');
  266. expect(trie.dump()).toStrictEqual([
  267. 'cdn.example.com', 'blog.cdn.example.com',
  268. '.skk.moe'
  269. ]);
  270. trie.whitelist('skk.moe');
  271. expect(trie.dump()).toStrictEqual(['cdn.example.com', 'blog.cdn.example.com']);
  272. trie.whitelist('cdn.example.com');
  273. expect(trie.dump()).toStrictEqual(['blog.cdn.example.com']);
  274. });
  275. });