import { expect } from 'chai' ; import 'mocha' ; import Config from '../src/config' ; import { StaticDataView } from '../src/data-view' ; import { noopOptimizeCosmetic , noopOptimizeNetwork , optimizeNetwork , } from '../src/engine/optimizer' ; import ReverseIndex from '../src/engine/reverse-index' ; import CosmeticFilter from '../src/filters/cosmetic' ; import IFilter from '../src/filters/interface' ; import NetworkFilter from '../src/filters/network' ; import { parseFilters } from '../src/lists' ; import { fastHash , tokenize } from '../src/utils' ; import { allLists } from './utils' ; describe ( 'ReverseIndex' , ( ) => { const { cosmeticFilters , networkFilters } = parseFilters ( allLists , { debug : true } ) ; [ new Config ( { enableCompression : true } ) , new Config ( { enableCompression : false } ) ] . forEach ( ( config ) => { describe ( `compression = ${ config . enableCompression }` , ( ) => { describe ( '#serialize' , ( ) => { function testSerializeIndexImpl < T extends IFilter > ( filters , deserialize , optimize ) { const reverseIndex = new ReverseIndex ( { config , deserialize , filters , optimize , } ) ; const buffer = StaticDataView . allocate ( reverseIndex . getSerializedSize ( ) , config ) ; reverseIndex . serialize ( buffer ) ; expect ( buffer . pos ) . to . equal ( buffer . buffer . byteLength ) ; buffer . seekZero ( ) ; const deserialized = ReverseIndex . deserialize ( buffer , deserialize , optimize , config ) ; expect ( deserialized ) . to . eql ( reverseIndex ) ; } it ( 'network (optimize = false)' , ( ) => { testSerializeIndexImpl ( networkFilters , NetworkFilter . deserialize , noopOptimizeNetwork ) ; } ) ; it ( 'network (optimize = true)' , ( ) => { testSerializeIndexImpl ( networkFilters , NetworkFilter . deserialize , optimizeNetwork ) ; } ) ; it ( 'cosmetic' , ( ) => { testSerializeIndexImpl ( cosmeticFilters , CosmeticFilter . deserialize , noopOptimizeCosmetic ) ; } ) ; } ) ; describe ( '#getFilters' , ( ) => { it ( 'network (optimize = false)' , ( ) => { expect ( new ReverseIndex ( { config , deserialize : NetworkFilter . deserialize , filters : networkFilters , optimize : noopOptimizeNetwork , } ) . getFilters ( ) ) . to . eql ( networkFilters ) ; } ) ; it ( 'network (optimize = true)' , ( ) => { expect ( new ReverseIndex ( { config , deserialize : NetworkFilter . deserialize , filters : networkFilters , optimize : optimizeNetwork , } ) . getFilters ( ) ) . to . eql ( networkFilters ) ; } ) ; it ( 'cosmetic' , ( ) => { expect ( new ReverseIndex ( { config , deserialize : CosmeticFilter . deserialize , filters : cosmeticFilters , optimize : noopOptimizeCosmetic , } ) . getFilters ( ) ) . to . eql ( cosmeticFilters ) ; } ) ; } ) ; for ( const optimize of [ noopOptimizeNetwork , optimizeNetwork ] ) { describe ( `optimize = ${ optimize !== noopOptimizeNetwork }` , ( ) => { it ( '#update' , ( ) => { const reverseIndex = new ReverseIndex ( { config , deserialize : NetworkFilter . deserialize , filters : parseFilters ( '||foo.com' , { loadCosmeticFilters : false , debug : true } ) . networkFilters , optimize , } ) ; let filters = reverseIndex . getFilters ( ) ; expect ( filters . map ( ( f ) => f . rawLine ) ) . to . eql ( [ '||foo.com' ] ) ; reverseIndex . update ( parseFilters ( '||bar.com' , { loadCosmeticFilters : false , debug : true } ) . networkFilters , undefined ) ; filters = reverseIndex . getFilters ( ) ; expect ( filters . map ( ( f ) => f . rawLine ) ) . to . eql ( [ '||foo.com' , '||bar.com' ] ) ; reverseIndex . update ( parseFilters ( '||baz.com' , { loadCosmeticFilters : false , debug : true } ) . networkFilters , new Set ( filters . map ( ( f ) => f . getId ( ) ) ) ) ; filters = reverseIndex . getFilters ( ) ; expect ( filters . map ( ( f ) => f . rawLine ) ) . to . eql ( [ '||baz.com' ] ) ; reverseIndex . update ( [ ] , new Set ( reverseIndex . getFilters ( ) . map ( ( f ) => f . getId ( ) ) ) ) ; expect ( reverseIndex . getFilters ( ) ) . to . eql ( [ ] ) ; } ) ; describe ( '#iterMatchingFilters' , ( ) => { const emptyIndex = new ReverseIndex ( { config , deserialize : NetworkFilter . deserialize , filters : [ ] , optimize , } ) ; const filters = `
||foo.com
/ads/tracker.js$image
|woot|$redirect=noop.js
      ` ; const exampleIndex = new ReverseIndex ( { config , deserialize : NetworkFilter . deserialize , filters : parseFilters ( filters , { loadCosmeticFilters : false , debug : true } ) . networkFilters , optimize , } ) ; it ( 'works on empty index' , ( ) => { let matches = 0 ; const cb = ( _ ) => { matches += 1 ; return true ; } ; emptyIndex . iterMatchingFilters ( new Uint32Array ( 0 ) , cb ) ; expect ( matches ) . to . equal ( 0 ) ; emptyIndex . iterMatchingFilters ( tokenize ( 'foo bar baz' , false , false ) , cb ) ; expect ( matches ) . to . equal ( 0 ) ; } ) ; it ( 'handle no match' , ( ) => { for ( let i = 0 ; i < 100 ; i += 1 ) { let matches = 0 ; const cb = ( _ ) => { matches += 1 ; return true ; } ; exampleIndex . iterMatchingFilters ( new Uint32Array ( [ i ] ) , cb ) ; expect ( matches ) . to . equal ( 0 ) ; } } ) ; describe ( 'finds matches' , ( ) => { const matches = new Set ( ) ; let ret = true ; const cb = ( f ) => { matches . add ( f . rawLine ) ; return ret ; } ; [ [ 'foo' , [ '||foo.com' ] ] , [ 'com' , [ ] ] , [ 'ads' , [ '/ads/tracker.js$image' ] ] , [ 'foo.ads' , [ '||foo.com' , '/ads/tracker.js$image' ] ] , [ 'woot' , [ '|woot|$redirect=noop.js' ] ] , [ 'https://bar.foo.com/ads/tracker.js' , [ '||foo.com' , '/ads/tracker.js$image' ] ] , ] . forEach ( ( [ input , expected ] ) => { describe ( `token=${ input }, expected=${ JSON . stringify ( expected ) }` , ( ) => { it ( 'get all matches' , ( ) => { matches . clear ( ) ; ret = true ; exampleIndex . iterMatchingFilters ( tokenize ( input as string , false , false ) , cb ) ; expect ( matches ) . to . eql ( new Set ( expected ) ) ; } ) ; it ( 'check early termination' , ( ) => { matches . clear ( ) ; ret = false ; exampleIndex . iterMatchingFilters ( tokenize ( input as string , false , false ) , cb ) ; expect ( matches . size ) . to . equal ( expected . length === 0 ? 0 : 1 ) ; } ) ; } ) ; } ) ; } ) ; it ( 'stores filters without tokens in wildcard bucket' , ( ) => { const index = new ReverseIndex ( { config , deserialize : NetworkFilter . deserialize , filters : parseFilters ( `
wildcard
||foo.com
      ` , { loadCosmeticFilters : false , debug : true } ) . networkFilters , optimize , } ) ; const matches = new Set ( ) ; const cb = ( f ) => { matches . add ( f . rawLine ) ; return true ; } ; [ [ 'foo' , [ '||foo.com' , 'wildcard' ] ] , [ 'com' , [ 'wildcard' ] ] , ] . forEach ( ( [ input , expected ] ) => { matches . clear ( ) ; index . iterMatchingFilters ( tokenize ( input as string , false , false ) , cb ) ; expect ( matches ) . to . eql ( new Set ( expected ) ) ; } ) ; } ) ; } ) ; describe ( '#getTokens' , ( ) => { it ( 'no token if empty' , ( ) => { expect ( new ReverseIndex ( { config , deserialize : NetworkFilter . deserialize , filters : [ ] , optimize , } ) . getTokens ( ) ) . to . eql ( new Uint32Array ( 0 ) ) ; } ) ; it ( 'returns all indexing tokens' , ( ) => { expect ( new ReverseIndex ( { config , deserialize : NetworkFilter . deserialize , filters : parseFilters ( `
/ads^
/foo^
-bar-
          ` ) . networkFilters , optimize , } ) . getTokens ( ) . sort ( ) ) . to . eql ( new Uint32Array ( [ fastHash ( 'ads' ) , fastHash ( 'foo' ) , fastHash ( 'bar' ) ] ) . sort ( ) ) ; } ) ; } ) ; } ) ; } } ) ; } ) ; } ) ;