diff options
| author | Árni Dagur <arni@dagur.eu> | 2020-07-27 20:35:50 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-07-27 20:35:50 +0000 |
| commit | 25684535e85a8bbb05b65b0da2304273d1d90ca0 (patch) | |
| tree | aad63588ac9eafe99ddd0d3ed847a3726d947271 | |
| parent | dc35e6044c249261dfc46eff831d5885a993eb0f (diff) | |
Update upstream library to 0.3.0, respond to API changes (#10)
* Update upstream library to 0.3.0, respond to API changes
* Change pypi publishing criteria in CI
* Create test to make sure the version numbers are the same everywhere
| -rw-r--r-- | .github/workflows/ci.yml | 7 | ||||
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | adblock/adblock.pyi | 28 | ||||
| -rw-r--r-- | pyproject.toml | 3 | ||||
| -rw-r--r-- | src/lib.rs | 150 | ||||
| -rw-r--r-- | tests/test_engine.py | 48 | ||||
| -rw-r--r-- | tests/test_typestubs.py | 53 | ||||
| -rw-r--r-- | tests/test_version_numbers.py | 28 |
8 files changed, 249 insertions, 72 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18cdc06..4071231 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml | |||
| @@ -73,8 +73,13 @@ jobs: | |||
| 73 | run: poetry run maturin develop --release | 73 | run: poetry run maturin develop --release |
| 74 | 74 | ||
| 75 | - name: Run Python tests | 75 | - name: Run Python tests |
| 76 | if: matrix.python-version != 3.5 | ||
| 76 | run: poetry run pytest -vv --color=yes | 77 | run: poetry run pytest -vv --color=yes |
| 77 | 78 | ||
| 79 | - name: Run Python tests (skip typestub tests) | ||
| 80 | if: matrix.python-version == 3.5 | ||
| 81 | run: poetry run pytest -vv --color=yes --ignore=tests/test_typestubs.py | ||
| 82 | |||
| 78 | python-publish: | 83 | python-publish: |
| 79 | needs: build | 84 | needs: build |
| 80 | runs-on: ${{ matrix.os }} | 85 | runs-on: ${{ matrix.os }} |
| @@ -138,7 +143,7 @@ jobs: | |||
| 138 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | 143 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 139 | 144 | ||
| 140 | - name: PyPi publish | 145 | - name: PyPi publish |
| 141 | if: startsWith(github.ref, 'refs/tags/') | 146 | if: github.event_name == 'release' && github.event.action == 'created' |
| 142 | env: | 147 | env: |
| 143 | MATURIN_PASSWORD: ${{ secrets.PYPI }} | 148 | MATURIN_PASSWORD: ${{ secrets.PYPI }} |
| 144 | run: poetry run maturin publish --interpreter python${{matrix.python_version}} --username __token__ | 149 | run: poetry run maturin publish --interpreter python${{matrix.python_version}} --username __token__ |
| @@ -1,7 +1,7 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | name = "adblock" | 2 | name = "adblock" |
| 3 | publish = false | 3 | publish = false |
| 4 | version = "0.2.3" | 4 | version = "0.3.0" |
| 5 | authors = ["Árni Dagur <arni@dagur.eu"] | 5 | authors = ["Árni Dagur <arni@dagur.eu"] |
| 6 | edition = "2018" | 6 | edition = "2018" |
| 7 | license = "MIT OR Apache-2.0" | 7 | license = "MIT OR Apache-2.0" |
| @@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0" | |||
| 10 | debug = true | 10 | debug = true |
| 11 | 11 | ||
| 12 | [dependencies] | 12 | [dependencies] |
| 13 | adblock = "0.2.9" | 13 | adblock = "0.3.0" |
| 14 | pyo3 = "0.10" | 14 | pyo3 = "0.10" |
| 15 | 15 | ||
| 16 | [lib] | 16 | [lib] |
diff --git a/adblock/adblock.pyi b/adblock/adblock.pyi index 50a61e8..82f753b 100644 --- a/adblock/adblock.pyi +++ b/adblock/adblock.pyi | |||
| @@ -11,7 +11,7 @@ class BlockerResult: | |||
| 11 | def __repr__(self) -> str: | 11 | def __repr__(self) -> str: |
| 12 | pass | 12 | pass |
| 13 | 13 | ||
| 14 | class HostnameSpecificResources: | 14 | class UrlSpecificResources: |
| 15 | hide_selectors: Set[str] | 15 | hide_selectors: Set[str] |
| 16 | style_selectors: Dict[str, List[str]] | 16 | style_selectors: Dict[str, List[str]] |
| 17 | exceptions: Set[str] | 17 | exceptions: Set[str] |
| @@ -19,14 +19,16 @@ class HostnameSpecificResources: | |||
| 19 | def __repr__(self) -> str: | 19 | def __repr__(self) -> str: |
| 20 | pass | 20 | pass |
| 21 | 21 | ||
| 22 | class FilterSet: | ||
| 23 | def __init__(self, debug: bool = False) -> None: | ||
| 24 | pass | ||
| 25 | def add_filter_list(self, filter_list: str, format: str) -> None: | ||
| 26 | pass | ||
| 27 | def add_filters(self, filters: List[str], format: str) -> None: | ||
| 28 | pass | ||
| 29 | |||
| 22 | class Engine: | 30 | class Engine: |
| 23 | def __init__( | 31 | def __init__(self, filter_set: FilterSet, optimize: bool = True) -> None: |
| 24 | self, | ||
| 25 | network_filters: Optional[List[str]] = None, | ||
| 26 | load_network: bool = True, | ||
| 27 | load_cosmetic: bool = False, | ||
| 28 | debug: bool = False, | ||
| 29 | ) -> None: | ||
| 30 | pass | 32 | pass |
| 31 | def check_network_urls( | 33 | def check_network_urls( |
| 32 | self, url: str, source_url: str, request_type: str | 34 | self, url: str, source_url: str, request_type: str |
| @@ -60,17 +62,17 @@ class Engine: | |||
| 60 | pass | 62 | pass |
| 61 | def deserialize_from_file(self, file: str) -> None: | 63 | def deserialize_from_file(self, file: str) -> None: |
| 62 | pass | 64 | pass |
| 63 | def add_filter_list(self, filter_list: str) -> None: | ||
| 64 | pass | ||
| 65 | def filter_exists(self, filter: str) -> bool: | 65 | def filter_exists(self, filter: str) -> bool: |
| 66 | pass | 66 | pass |
| 67 | def tags_enable(self, tags: List[str]) -> None: | 67 | def use_tags(self, tags: List[str]) -> None: |
| 68 | pass | ||
| 69 | def enable_tags(self, tags: List[str]) -> None: | ||
| 68 | pass | 70 | pass |
| 69 | def tags_disable(self, tags: List[str]) -> None: | 71 | def disable_tags(self, tags: List[str]) -> None: |
| 70 | pass | 72 | pass |
| 71 | def tag_exists(self, tag: str) -> bool: | 73 | def tag_exists(self, tag: str) -> bool: |
| 72 | pass | 74 | pass |
| 73 | def hostname_cosmetic_resources(self, hostname: str) -> HostnameSpecificResources: | 75 | def url_cosmetic_resources(self, url: str) -> UrlSpecificResources: |
| 74 | pass | 76 | pass |
| 75 | def hidden_class_id_selectors( | 77 | def hidden_class_id_selectors( |
| 76 | self, classes: List[str], ids: List[str], exceptions: Set[str] | 78 | self, classes: List[str], ids: List[str], exceptions: Set[str] |
diff --git a/pyproject.toml b/pyproject.toml index d92a78f..099e239 100644 --- a/pyproject.toml +++ b/pyproject.toml | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | [tool.poetry] | 1 | [tool.poetry] |
| 2 | name = "adblock" | 2 | name = "adblock" |
| 3 | version = "0.2.3" | 3 | version = "0.3.0" |
| 4 | description = "Brave's adblocking in Python" | 4 | description = "Brave's adblocking in Python" |
| 5 | authors = ["Árni Dagur <arni@dagur.eu>"] | 5 | authors = ["Árni Dagur <arni@dagur.eu>"] |
| 6 | license = "MIT OR Apache-2.0" | 6 | license = "MIT OR Apache-2.0" |
| @@ -14,3 +14,4 @@ python = "^3.5" | |||
| 14 | [tool.poetry.dev-dependencies] | 14 | [tool.poetry.dev-dependencies] |
| 15 | maturin = "*" | 15 | maturin = "*" |
| 16 | pytest = "*" | 16 | pytest = "*" |
| 17 | toml = "*" | ||
| @@ -12,8 +12,10 @@ | |||
| 12 | 12 | ||
| 13 | use adblock::blocker::BlockerError as RustBlockerError; | 13 | use adblock::blocker::BlockerError as RustBlockerError; |
| 14 | use adblock::blocker::BlockerResult as RustBlockerResult; | 14 | use adblock::blocker::BlockerResult as RustBlockerResult; |
| 15 | use adblock::cosmetic_filter_cache::HostnameSpecificResources as RustHostnameSpecificResources; | 15 | use adblock::cosmetic_filter_cache::UrlSpecificResources as RustUrlSpecificResources; |
| 16 | use adblock::engine::Engine as RustEngine; | 16 | use adblock::engine::Engine as RustEngine; |
| 17 | use adblock::lists::FilterFormat; | ||
| 18 | use adblock::lists::FilterSet as RustFilterSet; | ||
| 17 | use pyo3::class::PyObjectProtocol; | 19 | use pyo3::class::PyObjectProtocol; |
| 18 | use pyo3::exceptions::ValueError as PyValueError; | 20 | use pyo3::exceptions::ValueError as PyValueError; |
| 19 | use pyo3::prelude::*; | 21 | use pyo3::prelude::*; |
| @@ -32,8 +34,9 @@ use std::io::{Read, Write}; | |||
| 32 | fn adblock(_py: Python<'_>, m: &PyModule) -> PyResult<()> { | 34 | fn adblock(_py: Python<'_>, m: &PyModule) -> PyResult<()> { |
| 33 | m.add("__version__", env!("CARGO_PKG_VERSION"))?; | 35 | m.add("__version__", env!("CARGO_PKG_VERSION"))?; |
| 34 | m.add_class::<Engine>()?; | 36 | m.add_class::<Engine>()?; |
| 37 | m.add_class::<FilterSet>()?; | ||
| 35 | m.add_class::<BlockerResult>()?; | 38 | m.add_class::<BlockerResult>()?; |
| 36 | m.add_class::<HostnameSpecificResources>()?; | 39 | m.add_class::<UrlSpecificResources>()?; |
| 37 | Ok(()) | 40 | Ok(()) |
| 38 | } | 41 | } |
| 39 | 42 | ||
| @@ -163,10 +166,71 @@ impl Into<BlockerError> for RustBlockerError { | |||
| 163 | } | 166 | } |
| 164 | } | 167 | } |
| 165 | 168 | ||
| 169 | fn filter_format_from_string(filter_format: &str) -> PyResult<FilterFormat> { | ||
| 170 | match filter_format { | ||
| 171 | "standard" => Ok(FilterFormat::Standard), | ||
| 172 | "hosts" => Ok(FilterFormat::Hosts), | ||
| 173 | _ => Err(PyErr::new::<PyValueError, _>("Invalid format value")), | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | /// Manages a set of rules to be added to an Engine. | ||
| 178 | /// | ||
| 179 | /// To be able to efficiently handle special options like $badfilter, and to | ||
| 180 | /// allow optimizations, all rules must be available when the Engine is first | ||
| 181 | /// created. FilterSet allows assembling a compound list from multiple | ||
| 182 | /// different sources before compiling the rules into an Engine. | ||
| 183 | #[pyclass] | ||
| 184 | #[text_signature = "($self, debug)"] | ||
| 185 | #[derive(Clone)] | ||
| 186 | pub struct FilterSet { | ||
| 187 | filter_set: RustFilterSet, | ||
| 188 | } | ||
| 189 | |||
| 190 | #[pymethods] | ||
| 191 | impl FilterSet { | ||
| 192 | /// Creates a new `FilterSet`. The `debug` argument specifies whether or | ||
| 193 | /// not to save information about the original raw filter rules alongside | ||
| 194 | /// the more compact internal representation. If enabled, this information | ||
| 195 | /// will be passed to the corresponding Engine. | ||
| 196 | #[new] | ||
| 197 | #[args(debug = false)] | ||
| 198 | pub fn new(debug: bool) -> Self { | ||
| 199 | Self { | ||
| 200 | filter_set: RustFilterSet::new(debug), | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | /// Adds the contents of an entire filter list to this FilterSet. Filters | ||
| 205 | /// that cannot be parsed successfully are ignored. | ||
| 206 | /// | ||
| 207 | /// The format is a string containing either "standard" (ABP/uBO-style) | ||
| 208 | /// or "hosts". | ||
| 209 | #[text_signature = "($self, filter_list, format)"] | ||
| 210 | #[args(filter_list, format = "\"standard\"")] | ||
| 211 | pub fn add_filter_list(&mut self, filter_list: &str, format: &str) -> PyResult<()> { | ||
| 212 | let filter_format = filter_format_from_string(format)?; | ||
| 213 | self.filter_set.add_filter_list(filter_list, filter_format); | ||
| 214 | Ok(()) | ||
| 215 | } | ||
| 216 | |||
| 217 | /// Adds a collection of filter rules to this FilterSet. Filters that | ||
| 218 | /// cannot be parsed successfully are ignored. | ||
| 219 | /// | ||
| 220 | /// The format is a string containing either "standard" (ABP/uBO-style) | ||
| 221 | /// or "hosts". | ||
| 222 | #[text_signature = "($self, filters, format)"] | ||
| 223 | #[args(filters, format = "\"standard\"")] | ||
| 224 | pub fn add_filters(&mut self, filters: Vec<String>, format: &str) -> PyResult<()> { | ||
| 225 | let filter_format = filter_format_from_string(format)?; | ||
| 226 | self.filter_set.add_filters(&filters, filter_format); | ||
| 227 | Ok(()) | ||
| 228 | } | ||
| 229 | } | ||
| 166 | /// Contains cosmetic filter information intended to be injected into a | 230 | /// Contains cosmetic filter information intended to be injected into a |
| 167 | /// particular hostname. | 231 | /// particular hostname. |
| 168 | #[pyclass] | 232 | #[pyclass] |
| 169 | pub struct HostnameSpecificResources { | 233 | pub struct UrlSpecificResources { |
| 170 | /// A set of any CSS selector on the page that should be hidden, i.e. | 234 | /// A set of any CSS selector on the page that should be hidden, i.e. |
| 171 | /// styled as `{ display: none !important; }`. | 235 | /// styled as `{ display: none !important; }`. |
| 172 | #[pyo3(get)] | 236 | #[pyo3(get)] |
| @@ -187,9 +251,9 @@ pub struct HostnameSpecificResources { | |||
| 187 | pub injected_script: String, | 251 | pub injected_script: String, |
| 188 | } | 252 | } |
| 189 | 253 | ||
| 190 | impl Into<HostnameSpecificResources> for RustHostnameSpecificResources { | 254 | impl Into<UrlSpecificResources> for RustUrlSpecificResources { |
| 191 | fn into(self) -> HostnameSpecificResources { | 255 | fn into(self) -> UrlSpecificResources { |
| 192 | HostnameSpecificResources { | 256 | UrlSpecificResources { |
| 193 | hide_selectors: self.hide_selectors, | 257 | hide_selectors: self.hide_selectors, |
| 194 | style_selectors: self.style_selectors, | 258 | style_selectors: self.style_selectors, |
| 195 | exceptions: self.exceptions, | 259 | exceptions: self.exceptions, |
| @@ -199,10 +263,10 @@ impl Into<HostnameSpecificResources> for RustHostnameSpecificResources { | |||
| 199 | } | 263 | } |
| 200 | 264 | ||
| 201 | #[pyproto] | 265 | #[pyproto] |
| 202 | impl PyObjectProtocol for HostnameSpecificResources { | 266 | impl PyObjectProtocol for UrlSpecificResources { |
| 203 | fn __repr__(&self) -> PyResult<String> { | 267 | fn __repr__(&self) -> PyResult<String> { |
| 204 | Ok(format!( | 268 | Ok(format!( |
| 205 | "HostnameSpecificResources<{} hide selectors, {} style selectors, {} exceptions, injected_javascript={:?}>", | 269 | "UrlSpecificResources<{} hide selectors, {} style selectors, {} exceptions, injected_javascript={:?}>", |
| 206 | self.hide_selectors.len(), | 270 | self.hide_selectors.len(), |
| 207 | self.style_selectors.len(), | 271 | self.style_selectors.len(), |
| 208 | self.exceptions.len(), | 272 | self.exceptions.len(), |
| @@ -231,7 +295,7 @@ impl PyObjectProtocol for HostnameSpecificResources { | |||
| 231 | /// | 295 | /// |
| 232 | /// [1]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType | 296 | /// [1]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType |
| 233 | #[pyclass] | 297 | #[pyclass] |
| 234 | #[text_signature = "($self, network_filters=None, load_network=True, load_cosmetic=False, debug=False)"] | 298 | #[text_signature = "($self, filter_set, optimize)"] |
| 235 | pub struct Engine { | 299 | pub struct Engine { |
| 236 | engine: RustEngine, | 300 | engine: RustEngine, |
| 237 | } | 301 | } |
| @@ -240,21 +304,9 @@ pub struct Engine { | |||
| 240 | impl Engine { | 304 | impl Engine { |
| 241 | /// Create a new adblocking engine | 305 | /// Create a new adblocking engine |
| 242 | #[new] | 306 | #[new] |
| 243 | #[args(network_filters="None", load_network=true, load_cosmetic=false, debug=false)] | 307 | #[args(filter_set, optimize = true)] |
| 244 | pub fn new( | 308 | pub fn new(filter_set: FilterSet, optimize: bool) -> Self { |
| 245 | network_filters: Option<Vec<String>>, | 309 | let engine = RustEngine::from_filter_set(filter_set.filter_set, optimize); |
| 246 | load_network: bool, | ||
| 247 | load_cosmetic: bool, | ||
| 248 | debug: bool, | ||
| 249 | ) -> Self { | ||
| 250 | let filters = network_filters.unwrap_or(Vec::new()); | ||
| 251 | let engine = RustEngine::from_rules_parametrised( | ||
| 252 | &filters, | ||
| 253 | load_network, | ||
| 254 | load_cosmetic, | ||
| 255 | debug, | ||
| 256 | true, | ||
| 257 | ); | ||
| 258 | Self { engine } | 310 | Self { engine } |
| 259 | } | 311 | } |
| 260 | 312 | ||
| @@ -404,43 +456,57 @@ impl Engine { | |||
| 404 | self.deserialize(&data) | 456 | self.deserialize(&data) |
| 405 | } | 457 | } |
| 406 | 458 | ||
| 407 | /// Add the contents of a block list file to the blocking engine. | ||
| 408 | #[text_signature = "($self, filter_list)"] | ||
| 409 | pub fn add_filter_list(&mut self, filter_list: &str) { | ||
| 410 | self.engine.add_filter_list(filter_list); | ||
| 411 | } | ||
| 412 | |||
| 413 | /// Checks if the given filter exists in the blocking engine. | 459 | /// Checks if the given filter exists in the blocking engine. |
| 414 | #[text_signature = "($self, filter)"] | 460 | #[text_signature = "($self, filter)"] |
| 415 | pub fn filter_exists(&self, filter: &str) -> bool { | 461 | pub fn filter_exists(&self, filter: &str) -> bool { |
| 416 | self.engine.filter_exists(filter) | 462 | self.engine.filter_exists(filter) |
| 417 | } | 463 | } |
| 418 | 464 | ||
| 419 | /// Enable the given tags | 465 | /// Sets this engine's tags to be _only_ the ones provided in tags. |
| 466 | /// | ||
| 467 | /// Tags can be used to cheaply enable or disable network rules with a | ||
| 468 | /// corresponding $tag option. | ||
| 420 | #[text_signature = "($self, tags)"] | 469 | #[text_signature = "($self, tags)"] |
| 421 | pub fn tags_enable(&mut self, tags: Vec<&str>) { | 470 | pub fn use_tags(&mut self, tags: Vec<&str>) { |
| 422 | self.engine.tags_enable(&tags); | 471 | self.engine.use_tags(&tags); |
| 423 | } | 472 | } |
| 424 | 473 | ||
| 425 | /// Disable the given tags | 474 | /// Sets this engine's tags to additionally include the ones provided in |
| 475 | /// tags. | ||
| 476 | /// | ||
| 477 | /// Tags can be used to cheaply enable or disable network rules with a | ||
| 478 | /// corresponding $tag option. | ||
| 426 | #[text_signature = "($self, tags)"] | 479 | #[text_signature = "($self, tags)"] |
| 427 | pub fn tags_disable(&mut self, tags: Vec<&str>) { | 480 | pub fn enable_tags(&mut self, tags: Vec<&str>) { |
| 428 | self.engine.tags_disable(&tags); | 481 | self.engine.enable_tags(&tags); |
| 429 | } | 482 | } |
| 430 | 483 | ||
| 431 | /// Check if the given tag exists | 484 | /// Sets this engine's tags to no longer include the ones provided in |
| 485 | /// tags. | ||
| 486 | /// | ||
| 487 | /// Tags can be used to cheaply enable or disable network rules with a | ||
| 488 | /// corresponding $tag option. | ||
| 489 | #[text_signature = "($self, tags)"] | ||
| 490 | pub fn disable_tags(&mut self, tags: Vec<&str>) { | ||
| 491 | self.engine.disable_tags(&tags); | ||
| 492 | } | ||
| 493 | |||
| 494 | /// Checks if a given tag exists in this engine. | ||
| 495 | /// | ||
| 496 | /// Tags can be used to cheaply enable or disable network rules with a | ||
| 497 | /// corresponding $tag option. | ||
| 432 | #[text_signature = "($self, tag)"] | 498 | #[text_signature = "($self, tag)"] |
| 433 | pub fn tag_exists(&self, tag: &str) -> bool { | 499 | pub fn tag_exists(&self, tag: &str) -> bool { |
| 434 | self.engine.tag_exists(tag) | 500 | self.engine.tag_exists(tag) |
| 435 | } | 501 | } |
| 436 | 502 | ||
| 437 | /// Returns a set of cosmetic filter resources required for a particular | 503 | /// Returns a set of cosmetic filter resources required for a particular |
| 438 | /// hostname. Once this has been called, all CSS ids and classes on a | 504 | /// url. Once this has been called, all CSS ids and classes on a |
| 439 | /// page should be passed to hidden_class_id_selectors to obtain any | 505 | /// page should be passed to hidden_class_id_selectors to obtain any |
| 440 | /// stylesheets consisting of generic rules. | 506 | /// stylesheets consisting of generic rules. |
| 441 | #[text_signature = "($self, hostname)"] | 507 | #[text_signature = "($self, url)"] |
| 442 | pub fn hostname_cosmetic_resources(&self, hostname: &str) -> HostnameSpecificResources { | 508 | pub fn url_cosmetic_resources(&self, url: &str) -> UrlSpecificResources { |
| 443 | self.engine.hostname_cosmetic_resources(hostname).into() | 509 | self.engine.url_cosmetic_resources(url).into() |
| 444 | } | 510 | } |
| 445 | 511 | ||
| 446 | /// If any of the provided CSS classes or ids could cause a certain generic | 512 | /// If any of the provided CSS classes or ids could cause a certain generic |
| @@ -449,7 +515,7 @@ impl Engine { | |||
| 449 | /// referencing those classes or ids, provided that the corresponding rules | 515 | /// referencing those classes or ids, provided that the corresponding rules |
| 450 | /// are not excepted. | 516 | /// are not excepted. |
| 451 | /// | 517 | /// |
| 452 | /// Exceptions should be passed directly from HostnameSpecificResources. | 518 | /// Exceptions should be passed directly from UrlSpecificResources. |
| 453 | #[text_signature = "($self, classes, ids, exceptions)"] | 519 | #[text_signature = "($self, classes, ids, exceptions)"] |
| 454 | pub fn hidden_class_id_selectors( | 520 | pub fn hidden_class_id_selectors( |
| 455 | &self, | 521 | &self, |
diff --git a/tests/test_engine.py b/tests/test_engine.py index 28e25e6..a3d2898 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py | |||
| @@ -1,40 +1,62 @@ | |||
| 1 | import adblock | 1 | import adblock |
| 2 | import pytest | 2 | import pytest |
| 3 | 3 | ||
| 4 | SMALL_FILTER_LIST = """ | ||
| 5 | ||wikipedia.org^ | ||
| 6 | ||old.reddit.com^ | ||
| 7 | ||lobste.rs^ | ||
| 8 | """ | ||
| 4 | 9 | ||
| 5 | def test_engine_arguments(): | 10 | |
| 6 | # None of these should panic | 11 | def empty_engine(): |
| 7 | adblock.Engine() | 12 | return adblock.Engine(adblock.FilterSet()) |
| 8 | adblock.Engine([]) | 13 | |
| 9 | adblock.Engine(network_filters=None) | 14 | |
| 10 | adblock.Engine(network_filters=[]) | 15 | def test_engine_creation_and_blocking(): |
| 11 | adblock.Engine(load_network=False, load_cosmetic=True, debug=False) | 16 | filter_set = adblock.FilterSet(debug=True) |
| 12 | adblock.Engine(debug=True) | 17 | filter_set.add_filter_list(SMALL_FILTER_LIST) |
| 18 | engine = adblock.Engine(filter_set=filter_set) | ||
| 19 | |||
| 20 | blocker_result_wikipedia = engine.check_network_urls( | ||
| 21 | url="https://wikipedia.org/img.png", | ||
| 22 | source_url="https://google.com/", | ||
| 23 | request_type="image", | ||
| 24 | ) | ||
| 25 | assert isinstance(blocker_result_wikipedia, adblock.BlockerResult) | ||
| 26 | assert blocker_result_wikipedia.matched | ||
| 27 | |||
| 28 | blocker_result_facebook = engine.check_network_urls( | ||
| 29 | "https://facebook.com/directory/img.png", | ||
| 30 | "https://old.reddit.com/r/all", | ||
| 31 | "image", | ||
| 32 | ) | ||
| 33 | assert isinstance(blocker_result_facebook, adblock.BlockerResult) | ||
| 34 | assert not blocker_result_facebook.matched | ||
| 13 | 35 | ||
| 14 | 36 | ||
| 15 | def test_serde_file(tmpdir): | 37 | def test_serde_file(tmpdir): |
| 16 | path = str(tmpdir / "cache.dat") | 38 | path = str(tmpdir / "cache.dat") |
| 17 | 39 | ||
| 18 | engine0 = adblock.Engine() | 40 | engine0 = empty_engine() |
| 19 | with pytest.raises(FileNotFoundError): | 41 | with pytest.raises(FileNotFoundError): |
| 20 | # We haven't created the cache.dat file, so we should get an exception | 42 | # We haven't created the cache.dat file, so we should get an exception |
| 21 | # when attempting to deserialize. | 43 | # when attempting to deserialize. |
| 22 | engine0.deserialize_from_file(path) | 44 | engine0.deserialize_from_file(path) |
| 23 | 45 | ||
| 24 | engine1 = adblock.Engine() | 46 | engine1 = empty_engine() |
| 25 | serialization_result = engine1.serialize_to_file(path) | 47 | serialization_result = engine1.serialize_to_file(path) |
| 26 | assert serialization_result is None | 48 | assert serialization_result is None |
| 27 | 49 | ||
| 28 | engine2 = adblock.Engine() | 50 | engine2 = empty_engine() |
| 29 | deserialization_result = engine2.deserialize_from_file(path) | 51 | deserialization_result = engine2.deserialize_from_file(path) |
| 30 | assert deserialization_result is None | 52 | assert deserialization_result is None |
| 31 | 53 | ||
| 32 | 54 | ||
| 33 | def test_serde(): | 55 | def test_serde(): |
| 34 | engine = adblock.Engine() | 56 | engine = empty_engine() |
| 35 | serialization_result = engine.serialize() | 57 | serialization_result = engine.serialize() |
| 36 | assert isinstance(serialization_result, bytes) | 58 | assert isinstance(serialization_result, bytes) |
| 37 | 59 | ||
| 38 | engine2 = adblock.Engine() | 60 | engine2 = empty_engine() |
| 39 | deserialization_result = engine2.deserialize(serialization_result) | 61 | deserialization_result = engine2.deserialize(serialization_result) |
| 40 | assert deserialization_result is None | 62 | assert deserialization_result is None |
diff --git a/tests/test_typestubs.py b/tests/test_typestubs.py new file mode 100644 index 0000000..6a8d75f --- /dev/null +++ b/tests/test_typestubs.py | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | import ast | ||
| 2 | import re | ||
| 3 | |||
| 4 | |||
| 5 | def read_stubfile(): | ||
| 6 | with open("adblock/adblock.pyi", encoding="utf-8") as file: | ||
| 7 | node = ast.parse(file.read()) | ||
| 8 | return node | ||
| 9 | |||
| 10 | |||
| 11 | def get_functions_and_methods(node): | ||
| 12 | functions = [n for n in node.body if isinstance(n, ast.FunctionDef)] | ||
| 13 | classes = [n for n in node.body if isinstance(n, ast.ClassDef)] | ||
| 14 | |||
| 15 | methods = {} | ||
| 16 | for c in classes: | ||
| 17 | methods[c.name] = [n for n in c.body if isinstance(n, ast.FunctionDef)] | ||
| 18 | |||
| 19 | return functions, methods | ||
| 20 | |||
| 21 | |||
| 22 | def pattern_exists_in_file(filename, regex): | ||
| 23 | """ | ||
| 24 | Checks if the given regex is present in the given file | ||
| 25 | """ | ||
| 26 | with open(filename, "r", encoding="utf-8") as f: | ||
| 27 | for line in f: | ||
| 28 | if re.search(regex, line): | ||
| 29 | return True | ||
| 30 | return False | ||
| 31 | |||
| 32 | |||
| 33 | def test_functions_and_methods_exist_in_rust(): | ||
| 34 | """ | ||
| 35 | Check that for each of the functions and methods present in the Python | ||
| 36 | typestub file, there is a line in `src/lib.rs` containing a matching | ||
| 37 | definition. Since we're doing a naive grep search, without access to the | ||
| 38 | Rust AST, there may be false negatives. | ||
| 39 | """ | ||
| 40 | stubfile_node = read_stubfile() | ||
| 41 | functions, methods = get_functions_and_methods(stubfile_node) | ||
| 42 | |||
| 43 | methods_flattened = [] | ||
| 44 | for class_methods in methods.values(): | ||
| 45 | methods_flattened += class_methods | ||
| 46 | |||
| 47 | for f in functions + methods_flattened: | ||
| 48 | if f.name.startswith("__"): | ||
| 49 | # Skip dunder methods since their names are the same for every | ||
| 50 | # class, making the test not particularly useful. They are also not | ||
| 51 | # marked `pub` in Rust. | ||
| 52 | continue | ||
| 53 | assert pattern_exists_in_file("src/lib.rs", r"pub fn {}".format(f.name)) | ||
diff --git a/tests/test_version_numbers.py b/tests/test_version_numbers.py new file mode 100644 index 0000000..6519ffa --- /dev/null +++ b/tests/test_version_numbers.py | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | import toml | ||
| 2 | import adblock | ||
| 3 | |||
| 4 | |||
| 5 | def get_version_value_poetry(): | ||
| 6 | with open("pyproject.toml", encoding="utf-8") as f: | ||
| 7 | pyproject_toml = toml.loads(f.read()) | ||
| 8 | return pyproject_toml["tool"]["poetry"]["version"] | ||
| 9 | |||
| 10 | |||
| 11 | def get_version_value_cargo(): | ||
| 12 | with open("Cargo.toml", encoding="utf-8") as f: | ||
| 13 | cargo_toml = toml.loads(f.read()) | ||
| 14 | return cargo_toml["package"]["version"] | ||
| 15 | |||
| 16 | |||
| 17 | def test_version_numbers_all_same(): | ||
| 18 | """ | ||
| 19 | Makes sure that `pyproject.toml` and `Cargo.toml` contain the same version | ||
| 20 | number as the one attached to the `adblock` module. | ||
| 21 | """ | ||
| 22 | cargo_version = get_version_value_cargo() | ||
| 23 | poetry_version = get_version_value_poetry() | ||
| 24 | module_version = adblock.__version__ | ||
| 25 | |||
| 26 | assert cargo_version == poetry_version | ||
| 27 | assert poetry_version == module_version | ||
| 28 | assert cargo_version == module_version | ||
